The shared layer is ported. What remains is the eleven catalogue
profiles and build().
Records what this session established that would be expensive to
rediscover: six-significant-figure report rounding and why VOLUME_MM3
makes it load bearing, the read-only Docker probe against the pinned
image, the oracle parameter defaults, and F-034 in full including why it
must not be fixed.
Also records the working method that earned its keep: read the pinned
source rather than recall it, mutation test every suite before landing
it, and deliver by upload rather than paste.
No behaviour change. Comments and a FAILURES entry.
The Y profile builds a section area of 135.572973 against a recorded
135.574, out by 0.001027, while every other value for that case matches
exactly. Three-Fin matches on everything including area.
Isolated by probing the reference inside the pinned toolchain image. The
hull cap is identical to nine figures, the bare union is identical, and a
single filleted pair is identical at 30 vertices and 125.699057 mm2. The
difference appears only when the three filleted pairs are combined, and
the three pairs, which are related by 120 degree symmetry and must be
identical, come back as 125.699057, 125.698029, 125.699057.
Cause proven. The arc segment count is a ceiling on a quantity that is
frequently an exact integer: a 60 degree half-angle at $fn=48 gives
exactly 8. Floating point delivers that as 8.000000000000004 on one
corner and 7.999999999999998 on the others, so one corner gets a whole
extra segment. The half-angles come from the merged polygon, whose
vertices come from the boolean kernel, and BOSL2 clipper and GEOS
disagree in the last bit.
Reproduced unguarded because the reference is unguarded. Rounding the
count before the ceiling was implemented and reverted: it makes the three
pairs identical and fixes Y exactly, and breaks Three-Fin, which had been
matching to the digit. Three-Fin has the same asymmetry and the oracle
records it. BOSL2 tipped the same way GEOS does there and the opposite
way on Y.
Two consequences for the project rather than the code. Some recorded
values encode float noise rather than geometry, so a port that is
geometrically more correct than the reference will fail those cases. And
the tolerance model may need revisiting: VOLUME_MM3 is compared at the
lengths tolerance of 1e-4 despite being area times 100 mm, so a 1e-3 area
difference becomes a 1e-1 volume difference. MASS_G is derived the same
way.
No decision yet. The number of affected cases is unknown and is the only
thing that should drive it, and that is not knowable until build() exists
and all 123 cases can run.
The PROFILE record, centred assembly, and three complete arrangements:
ring, spokes, fins. Each written for N members, exercised at N=3 and N=4.
Failure travels as an empty profile carrying one failing check, as in the
reference, so profile rejections and universal-check rejections stay in
one order-sensitive list and the first failure is what surfaces.
The section is cleaned before it is measured, not after. A Three-Fin
section carries 17 collinear vertices from exact butt joints; they are
harmless in 2D and leave zero-area triangles the tessellator cannot
resolve, so measuring first would report on geometry that is not what
gets extruded.
Centred now carries the shifted members. Measuring a shifted shell
against unshifted members reports every cavity as escaping the envelope,
a leak of the whole cavity area from geometry that is fine. That trap
caught me while smoke testing, so the opportunity is removed rather than
documented.
Verified against the oracle where the fillet does not affect the result:
SPOKE_RADIUS_MM 9.1713, FIN_CORE_SIDE_MM 13.4028, FIN_SETBACK_MM
3.77783, FIN_JUNCTION_WEB_MM 2.29919, RING_CORNER_R_MAX_MM 2.87663, the
ring edge vector, and both envelope dimensions all match to the recorded
digit. The solvers are right.
OPEN: with a junction fillet of 1.5 the Y section area is 135.572973
against a recorded 135.574, off by 0.001027 and just past the area
tolerance, while every other quantity for that case matches exactly.
Either the generator fillet default is not 1.5, or there is a difference
of about seven parts per million concentrated in the fillet. The
generator settles it.
35 tests. Nine mutations, two of which found real gaps: nothing asserted
that cleaning removed anything, and the spoke zero-fillet check was
untested.
Oracle acceptance still skips; 236 unchanged.
Checks as values, the five universal checks, metrics, ProfileRejected,
and the report block. Completes the shared layer; only the profiles and
build() remain.
Report numbers are rounded to six significant figures on the way out,
matching OpenSCAD echo, which is C %g at default precision. The oracle
records what OpenSCAD printed, not full-precision geometry.
This is load bearing rather than cosmetic. VOLUME_MM3 ends in _MM3, so
test_oracle.py compares it at the lengths tolerance of 1e-4 and not the
areas tolerance of 1e-3. Volume is section area times a 100 mm length,
so an unrounded port reporting 13557.402 against a recorded 13557.4
fails by twenty times the tolerance while being geometrically correct.
Verified against the oracle: across all 113 accepted cases the recorded
volume equals the rounded area times length to within 3.6e-12, which
holds only if volume is computed unrounded and rounded at print. That is
what this layer does.
The consequence is that geometry must agree with the reference to better
than one part in a million before rounding. Near a rounding boundary a
smaller error can still tip the last digit, and that will show up as a
single case failing by one unit in the last place rather than as
something mysterious.
28 tests. Nine mutations, all caught first pass, including rounding to
decimal places instead of significant figures and computing volume from
the already-rounded area.
Oracle acceptance still skips; 236 unchanged.
Face lines, structural butt joints, hull caps, concave fillets, the
derived bore, ring fit, ring envelope and section assembly. N-generic
throughout, as the reference is.
Junctions are structural, not cosmetic: one sleeve runs through its
neighbour and is cut flush against that member the far surface, so the
two share a full-width overlap whether or not a fillet is applied on
top. The bore is derived from the members own inside-wall lines rather
than a separately scaled shape, which is what makes the declared inside
wall exactly what remains beside each cavity.
43 tests. Mutation testing found two of the reference own warnings to be
load-bearing and untested by me. A bore that has turned inside out can
carry over a square millimetre of area, so the area guard alone accepts
it and only the interior-side test rejects it. And the ring fit really
does have a spurious lower branch: a thin triangle meets a 1.2 mm web at
relative scale 0.425, where members overhang their own corners and the
solve looks converged. Both now covered.
A third mutation was malformed on my part rather than a gap -- cutting
the cavities twice is idempotent -- and was replaced with one that does
change behaviour. Nine mutations caught.
Oracle acceptance still skips; 236 unchanged.
Booleans, decomposition, area, simplicity, hull, bounds, mitred offset
and vertex cleaning. Booleans go to GEOS, which is the reason Shapely was
chosen. The decomposition does not.
BOSL2 region_parts counts by nesting parity, not connectivity: a path
takes a level from how many others contain the midpoint of its first
edge, even levels are outer boundaries, their odd children are holes.
SECTION_PARTS == 1 is an exact assertion, and Shapely agreeing with that
count is a coincidence that holds for well-formed input and not
otherwise, so the decomposition is transcribed and both the part count
and the area derive from it.
is_region_simple is treated as a manifold precondition rather than a
diagnostic. An outline that touches itself measures perfectly and cannot
be tessellated, so it must fail here and not at export.
Developed against Shapely 2.1.2 / GEOS 3.13.1, matching CT 100. Boolean
results on near-degenerate geometry can shift between GEOS releases; if
the oracle ever disagrees by one part after an upgrade, look there first.
39 tests, all arithmetic on rectangles. Mutation run found a real gap:
nothing distinguished on-boundary from outside in the nesting probe until
a shared-edge case was added. Eight mutations now caught.
Oracle acceptance still skips; 236 unchanged.
Shapely has no corner rounding, so the round_corners -> _circlecorner ->
arc -> segs chain is transcribed from BOSL2 at the pinned commit
92d697c2, read from source rather than recalled. Also deduplicate,
path_merge_collinear, is_collinear and approx, which the cleanup path
depends on.
Segment counts are contract, not a quality setting. An arc becomes
straight segments and the count sets the enclosed area, compared against
the oracle at 1e-3 mm2 -- and the extruded solid is those segments, so
this is the definition of the surface. Both generators set $fn = facets
with facets = 48 and no oracle case overrides it, so segmentation
depends on swept angle alone. A right angle gives 12 points.
round_corners raises where BOSL2 asserts, rather than clamping: silently
fitting a roundover the reference refused would diverge without any
visible failure. sb_corner_radii exists to derive safe radii up front.
33 tests. The exact-fit boundary raises rather than passing, because
tan(45) is under 1 in both languages -- a test asserting the tidy
behaviour would have looked right and been wrong. Mutation run found a
real gap: nothing exercised the three-point floor on blunt corners until
a 170-degree case was added. Seven mutations now caught.
Oracle acceptance still skips; 236 unchanged.
Vectors, GEO and MEMBER records, member placement, sleeve and cavity
paths, exact polyline distance, corner-radius derivation, and the
monotone solver. Direct translation of legacy/openscad/lib/sb-geom.scad
at rev 8.0.0.
Angles stay in degrees, matching OpenSCAD, so every expression reads the
same as its source line. The 44 solver iterations, the 0.999 and 0.98
scale factors, the 0.05/179.95 cutoffs and the 1e9 sentinel are
reproduced exactly: they shaped the frozen oracle.
Region operations are not included -- they need a 2D boolean kernel and
follow with the Shapely layer.
49 unit tests, none of which touch the oracle. Harness proven by
mutation: radians for degrees, a shortened solver, a dropped scale
factor, a skipped crossing test and a flipped offset sign are each
caught. Oracle acceptance still skips; 236 unchanged.
set -euo pipefail aborted the script on the diff pipeline one line
before the cp that restores the pre-run oracle, so --full left a
regenerated fixture file in the working tree while printing that
nothing had been overwritten. Appended || true.
Recorded as F-033. The fix is not yet exercised: the restore branch
runs only under --full and has not been entered since the change.
Reference implementation of the strap-beam generators at revision 8.0.0, kept
so the acceptance oracle can be regenerated. Not a live target; the running
application has no OpenSCAD dependency.
The oracle holds 123 frozen cases, 113 accepted and 10 rejected, produced by
OpenSCAD 2021.01 with BOSL2 at 92d697c2. The ten rejections are part of the
contract: a port that accepts them is wrong.
tests/test_oracle.py specifies the port API and was written before the port,
so the interface follows from what must be verified rather than what is
convenient to implement. Proven by adversarial stub: a build() that rejects
everything passes all 10 rejection tests and fails all 226 acceptance tests.