Part of #6 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT
118 lines
6.7 KiB
Markdown
118 lines
6.7 KiB
Markdown
# 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
|
||
```
|
||
|
||
- **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.
|