Skip to content

Release fixes before 3.0.0: area error bound, lighter sphere meshes, exact turned boxes, cone apex refusal - #32

Merged
aka-blackboots merged 10 commits into
mainfrom
release-fixes
Oct 31, 2026
Merged

aka-blackboots merged 10 commits into
mainfrom
release-fixes

Conversation

@aka-blackboots

Copy link
Copy Markdown
Contributor

Fixes that go into 3.0.0 before it is published. 3.0.0 is not on npm yet, so the version stays 3.0.0 and the MIGRATION "2.5 to 3.0" section describes all of it.

What a user sees

  • Area error bound. body.measure() also returns areaErrorBound: |area − exact| ≤ areaErrorBound. Before, nothing bounded the area, and below unit size the area error was up to several thousand times errorBound. In 201 test rows the worst area error is 0.89 of the bound. measure is 8 % to 15 % slower.
  • Lighter sphere and torus meshes. At the same deflection a sphere of radius 0.5 goes from 54,288 to 6,888 triangles, a torus from 47,672 to 7,728. The mesh still deviates at most 0.5 of the requested deflection.
  • Exact boxes on turned frames. getBounds() of a circle or ellipse edge, a cylinder or cone face and a full torus on a turned frame is exact. A chair arm mirrored across a slanted plane was 0.0116 m too wide.
  • Cone apex refusal. A boolean whose intersection reaches a cone apex throws CoverageGap (message names 'singular surface point'), not InvalidGeometry.
  • Shell. New tests pin the inset neck rules; the MIGRATION shell entry states them exactly. No code change.
  • MIGRATION rows from 2.0 Sphere, shell, classifyPoint and normalAtFace to their 3.0 calls.
  • Build. A local npm run build starts from an empty dist, so old chunks cannot ship in a local pack.

Issues

OPE-89, OPE-90, OPE-84, OPE-47, OPE-85, OPE-83, OPE-86.

Checks

  • Each fix passed npm run check:full (20 of 20 steps) before it merged into this branch, with a kernel snapshot compared against the one before.
  • The last commit changes only documents and returns the version to 3.0.0; the README and MIGRATION code test and the dist package test pass, and check:source is clean.

Known limits (filed, not in this PR)

  • OPE-98: classifyPoint returns Inside for some points outside a mirrored cylinder, cone or frustum.
  • OPE-99: on trimmed curved faces the mesh can deviate up to almost the full deflection.
  • OPE-100: a cuboid minus a pointed cone through its apex gives UnresolvedIntersection.
  • OPE-105: trimmed curved faces refuse to tessellate at fine deflection; areaErrorBound is loose on a torus and can be Infinity for a triangle too coarse to bound.
  • OPE-106: a shell of a rounded box with a corner radius just above the wall throws InvalidTopology.

After merge

The v3.0.0 tag points at fbef715, before these fixes. Move it to the merge commit before publishing 3.0.0.

… normalAtFace (OPE-89)

The "Removed in 2.5" list names 2.0 Sphere, shell, classifyPoint and
normalAtFace, but the 2.5 to 3.0 table did not say which 3.0 call takes
their place. Add rows for Sphere, shell and classifyPoint with the
differences from 2.0 (default radius, shell in place, local point and
capitalised results), mark the existing normalAtFace row as a 2.0 name
removed in 2.5, and point the removed list at the table.
The build ran rollup into dist with no clean step, so a long-lived local
dist kept old body-registry chunks and a local npm pack shipped them.
clean-dist.mjs removes dist after build-core and before rollup; a node
test asserts every top-level script in dist is reachable from an entry.
developer.md now describes the clear step.
… (OPE-84)

A plane through a pointed cone's axis meets the cone in two rulings that
end at the apex. The universal SSI took the branch-end tangent from the
cross product of the surface normals, and the cone has no normal at its
apex, so the call threw InvalidGeometry (SingularParameterization).
No handler builds a cone minus a half-space today, so the case now
refuses like the frustum: regular_normal maps a singular point at a
branch end to CoverageGap, and details.families names the pair.
Review (class 2): 54 probe runs, 9 changed, all from InvalidGeometry to
CoverageGap; none changed between built and refused.
… (OPE-47)

The sphere and torus grid rules were about 2.8 times too dense per
direction: a sphere of radius 0.5 had 54,288 triangles at the static
display bucket, with a worst deviation of 6 % of the requested one.
Each lat-long quad is a flat trapezoid, so its deviation is at most the
widest parallel's sag plus the meridian's sag; the new counts keep each
at e/2: sphere sqrt(r/(4e)) per direction, torus sqrt((R+a)/(4e)) and
sqrt(a/(4e)). The OPE-29 floor is unchanged. Measured worst deviation
0.477 D (sphere r 0.5, 6,888 triangles) and 0.486 D (torus 1/0.25,
7,728 triangles).

111 of 581 golden keys move, all sphere or torus meshes, display
buffers and mesh volumes. The stored sphere, torus and revolved-bowl
meshes (recorded from 2.0) are re-recorded from this kernel. Two sphere
boolean volume checks now measure at deflection 0.005, inside their
unchanged tolerances. Review (class 3): grid path within 0.499 D in
every probe; measure volume error at most 0.069 of errorBound.
…s and tori (OPE-85)

A circle or ellipse edge, and a cylinder, cone or torus face, on a frame
turned against the world axes was boxed through its turned local box.
A side was loose when both frame vectors fed one world axis: a mirrored
chair arm was 0.0116 m too wide, a full torus up to 1.08 times its
major radius. Each world axis now gets the exact range of
origin + P cos t + Q sin t (plus the axial term for faces), in floats
with an outward pad of 16 eps of the magnitude, intersected with the
old box so no box gets wider. Sphere faces and partial tori are as
before.

The box drives the display bucket, the classification grid and the
exchange error bound, so 13 scenes move: grid points, a one-step finer
display bucket for the turned torus, bounds of the mirrored graph, and
exchange error bounds near 1e-11. No classification verdict changes at
a fixed point (344,605 points checked). Review (class 2): worst sample
outside a box 2.3e-17, the same as the unchanged path; box cost +3.6 to
+7.5 %, classification -0.6 %.
check:duplicates found the two full-circle tests identical after identifier normalising; both now call one helper that takes the frame.
…(OPE-83)

measure() returned one errorBound, documented as the volume bound
(4 * area * deflection). It has volume units, so it never bounded the
area: at size 1e-3 every curved primitive missed it by 7x to 96x. The
volume bound holds (worst |error|/bound 0.089 over the corpus).

Measure gains areaErrorBound (owner-approved name; the binding appends
it as the fifth value). Per triangle it bounds the area against the
surface patch with the offset-surface Jacobian and signed projection,
which holds for folded triangles; each curved trim chord adds a lune
term per face beside the edge; a 1e-10 * area floor covers rounding.
A triangle too coarse to bound makes it Infinity. volume, area,
errorBound and deflection are bit-identical (258 corpus rows).

Corpus: 201 rows measure, worst area error / areaErrorBound 0.888; the
57 refusals (LimitExceeded, UnresolvedTessellation) are unchanged and
out of scope. Review (class 3): derivation sound, 34 closed-form probes
pass; one fix round for an azimuth rounding case that gave Infinity on
slot and rounded-rectangle extrusions. Cost: +8 to +15 %.
OPE-36 left open whether the shell's line-and-arc inset misses a pinch
between neighbouring edges away from their shared corner. A probe over
seven profile families and necks from 0 to 1e-4 found no such pinch:
the inset is an erosion with only convex corners, and the distance from
a line or circle to another has one local minimum, so neighbours can
only come close again by crossing, which splits or refuses. No code
change. Three tests pin the rules (proven by four mutants), and
MIGRATION states what is detected: edges that come closer than four
times the geometric tolerance and do not share a corner; any inset
feature below the inset tolerance can also throw.
Review (class 2): about 90 runs over 15 families, every built body
valid; one wording fix round.
3.0.0 is not published, so the batch ships as part of it. The version stays 3.0.0. MIGRATION has no 3.0.0 to 3.0.1 section: areaErrorBound, the scoped sync flush and its input checks, the cone apex refusal and the revolve shell families move into 2.5 to 3.0, with three new checks. The pointed cone line in the half-space note now says CoverageGap.
@aka-blackboots
aka-blackboots merged commit f50cf4d into main Oct 31, 2026
0 of 4 checks passed
@aka-blackboots
aka-blackboots deleted the release-fixes branch October 31, 2026 11:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant