Files
solidcraft-upstream/docs/robustness.md
T

122 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Kernel robustness
Real users hit edge cases the Fusion oracle (docs/oracle.md) never sees: faces that coincide
by accident, tangencies, seams lying where another body cuts, walls thinner than a shell. The
robustness runs build random but realistic designs and check every step.
## How it runs
```
solvecraft-cli fuzz --from 0 --count 1000 --jobs 8 --timeout 90 --out runs/ --report report.md
solvecraft-cli fuzz-one <seed> --minimise # one design, cut down to the steps that matter
```
`--count` seeds are run starting at `--from`. A range that exceeds `u64::MAX` is
rejected before any jobs or output directories are created. The maximum seed
(`18446744073709551615`) is valid with `--count 1`; `--count 0` runs no seeds.
- **Designs** (`crates/engine/src/fuzz.rs`): a seeded generator starts from a box, an extruded
sketch profile closed by an arc (sometimes notched), or a revolved section (partial or full
turns, sometimes touching the axis). It then adds up to eight steps: joins and cuts of
boxes, cylinders and spheres, fillets and chamfers on random edges, shells, holes (simple,
blind, drilled), and rectangular and circular patterns of the last feature. Placements snap
to a 2.5 mm grid and often to the body's own faces, so coincident faces and tangencies come
up all the time, as they do in real parts.
- **Checks after every step:** the command succeeds; every body is a sound solid (each shell
closed and consistently oriented, every face meshes, positive finite volume:
`Body::validity`); the volume moves the right way (a cut never adds, a join never removes).
- **Boolean identity** (`crates/kernel/src/fuzz.rs`): for two random primitives on the same
grid (boxes, cylinders, spheres, tori), union, intersection and cut each give a sound solid
and V(A∪B) + V(A∩B) = V(A) + V(B), V(A−B) = V(A) − V(A∩B), within 0.2 %.
- Each seed runs in its own process with a time limit, so hangs and hard crashes are recorded
too. A step the kernel refuses as **not supported yet** (a fillet between two curved faces,
a shell thicker than a wall) is undone and counted separately, and the design goes on: those
are known limitations, not failures. Booleans that fail count as failures even when their
message says "not supported".
- `cargo test` runs a fixed set of seeds that must stay clean
(`crates/engine/src/tests/robustness.rs`), next to the minimised cases fixed so far.
## Results
1000 seeds (0–999), 8 processes on a heavily loaded machine (load average 60–80), 90 s per
seed. A "timeout" there is mostly a design whose booleans are very slow (seed 20 finishes in
2 min 8 s alone), not a hang. The "after" run had more of them: the machine was busier still
(test suites ran alongside), and failing booleans now try more fallbacks before giving up;
fewer designs fail outright (27.1 % against 36.3 %). Both figures are from before the last two
fixes below (sound face pushes, no panics in the boolean's final assembly).
| Designs | before | after |
|---|---|---|
| clean | 49.8 % | 53.8 % |
| failed | 36.3 % | 27.1 % |
| invalid body | 0.7 % | 0.9 % |
| volume the wrong way | 0.2 % | 0.2 % |
| over 90 s | 13.0 % | 18.0 % |
| Boolean identity | before | after |
|---|---|---|
| holds | 85.4 % | 80.8 % |
| a boolean failed | 1.6 % | 1.2 % |
| not run (the design ran out of time) | 13.0 % | 18.0 % |
### Fixed
- **Shell corners where a curved face meets another at an angle** (the biggest cause, 215 of
the 363 failed designs before). Moving a vertex took one linear step, exact only for planes;
a cylinder meeting a plane at an angle (a shallow arc side) landed off the moved surface.
Now refined by Newton steps onto the moved surfaces.
- **Shell edges that are neither lines nor arcs** (a hole's wall meeting a curved side): the
edge is rebuilt through its moved points.
- **Shells of bodies in several pieces** ("this shell is not connected"): one shell per piece.
- **Cylinder seams on the grid.** A cylinder's seam lines lay on its frame's axes, exactly
where faces placed on the same grid cut it, and booleans failed along the seam. Seams now
start at an odd angle: cylinders tangent to box faces from inside or out, and crossing
cylinders touching along a line, now work.
- **Partial revolves of profiles touching the axis** (a quarter or half turn of a rectangle
from the axis): the edge on the axis no longer sweeps a zero-area face, which every boolean
attempt failed on.
- **Booleans on bodies in several pieces** (a join that left two lumps): when the whole fails
or comes out empty, a piece at a time.
- **Pushing a face apart** (coincident faces) checks its result: the exact rebuild could leave a
cylinder unsound, and then the general offset is used. A boss standing on a plate joins again.
- **No panics in the boolean's final assembly.** Natively a panic inside the kernel is caught,
but the web build aborts on any panic; the Plastic Enclosure sample panicked 8 times building
its bosses and crashed the start page in the browser. The vendored shapeops now returns
`None` instead, and a test builds every sample with a panic counter. The runs count caught
panics per design ("Caught panics" in the report), since each one would crash the web build.
### The first tutorial (box, fillets, shell)
Found by QA on new users' first steps, each with an engine test against the analytic volume
(`crates/engine/src/tests/robustness.rs`):
- **Shell of a filleted box** (vertical edges r5, bottom loop r3, 2 mm, top open): the bottom
round runs round the corners as spindle tori (the tube wider than its circle), which the
offset now takes. Volume within 1e-5 of the analytic one.
- **Shell thicker than or equal to a round** (r2 rounds shelled 2 mm): the round's inside
collapses to a sharp edge, as in Fusion, instead of "a cylinder shrinks to nothing".
- **Shell thinner than a round tangent to the open face** (1.5 mm): every face moves in
(tangencies keep) and the opening is the open face's copy swept out through the body.
- **Shell after a drilled hole** (its tip a cone).
- **Filleting a box's edges one at a time**: an edge that runs into an earlier round takes
that round off and rounds both together, with the corner where they meet (`blend_corner.rs`,
any radii).
### Speed
- Measuring a body of 5000 faces (a modelled thread): 13 s to 0.8 s.
### Known limitations (counted as "not supported", with a clear message)
- A shell thicker than a wall or a step: the moved faces would cross and the cavity turn
inside out. Fusion fills such places solid; we need a topology-changing offset.
- Fillets and chamfers between two curved faces, or ending at faces that aren't planar.
### Still failing
- Partial unions of a cap and a face (a cylinder's end lying partly on a box face): pushing
one face into the other adds material where they don't overlap.
- Shells of partial annuli (a half ring) whose cavity truck's boolean rejects although the
same cavity built another way cuts cleanly.
- Very slow booleans on bodies with many patterned features.