# HANDOFF — 2026-08-19 For the assistant taking over. Read this, then the documents it points to. The 18 AUG handoff still applies in full: the operator's constraint, the working discipline, and everything in §7 "Things that will bite you". This document adds what one session of actual porting established. It does not replace it. --- ## 1. Read these first, in this order All in `mechanical-compiler/docs/` at commit `86a47c3`. | # | File | Why | |---|---|---| | 1 | `HANDOFF-2026-08-18.md` | Still current on process, constraints, and hazards | | 2 | `PROCESS.md` | How work is done here. Read before issuing any command. | | 3 | `FAILURES.md` | 34 entries now. F-033 and F-034 are from this session. | | 4 | `STAGING-STATE.md` | What is true on `srv-b` | `ENVIRONMENT.md` and `ROADMAP.md` as needed. The port work is development mode, not infrastructure mode — see `PROCESS.md` §2. --- ## 2. Where the port stands **The entire shared layer is ported, tested and pushed.** Six slices, each committed separately with its own tests, each proven by mutation before landing. | Module | From | What it holds | |---|---|---| | `geom/primitives.py` | `sb-geom.scad` | Vectors, GEO and MEMBER, sleeve and cavity paths, exact polyline distance, corner-radius derivation, the monotone solver | | `geom/rounding.py` | BOSL2 `92d697c2` | `round_corners`, `_circlecorner`, `arc`, `segs`, `deduplicate`, `path_merge_collinear` | | `geom/region.py` | BOSL2 regions | Shapely-backed booleans, nesting-parity decomposition, area, simplicity, hull, mitred offset, cleaning | | `geom/join.py` | `sb-join.scad` | Butt joints, hull caps, fillets, the derived bore, ring fit, section assembly | | `geom/report.py` | `sb-report.scad` | Checks-as-values, metrics, the five universal checks, `ProfileRejected`, report formatting | | `geom/core.py` | `sb-core.scad` | The PROFILE record, failure representation, centred assembly | | `geom/arrangements.py` | `sb-profiles.scad` | The three N-generic arrangements: ring, spokes, fins | **230 unit tests, none of which touch the oracle.** The 236 oracle acceptance tests still skip: `mechcomp.profiles.build` does not exist yet. ### What remains Read `strap-beam-3x.scad` and `strap-beam-4x.scad`. Port the eleven catalogue profiles and the `build()` entry point into `src/mechcomp/profiles/`. Then the oracle runs for the first time. **The port's public entry point is `mechcomp.profiles`, not `mechcomp.geom`.** `conftest.py` does `importorskip("mechcomp.profiles")` and requires a `build` attribute on it. The geometry lives under `mechcomp.geom`; `build()` and `ProfileRejected` must be exported from `mechcomp.profiles`. --- ## 3. Things established this session that are expensive to rediscover ### Report values are rounded to six significant figures The oracle records what OpenSCAD's `echo` printed, which is C's `%g` at default precision — not full-precision geometry. `echo_num()` in `geom/report.py` does this; `echo_vec()` handles vectors, which reach the oracle as strings like `'[20.5209, 20.5209, 20.5209]'`. This is load-bearing, not cosmetic. `VOLUME_MM3` ends in `_MM3`, so `test_oracle.py` compares it at the **lengths** tolerance of 1e-4 rather than 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: across all 113 accepted cases the recorded volume equals the rounded area times length to within 3.6e-12. ### Angles are degrees, and the arbitrary constants are contract OpenSCAD trigonometry is in degrees. The port keeps degrees throughout with explicit `cos_d`/`sin_d`/`tan_d` helpers so every expression matches its source line. The 44 solver iterations, the 0.999 and 0.98 scale factors, the 0.05/179.95 degree cutoffs and the 1e9 sentinel are reproduced exactly. They are not tidy numbers to improve on; they produced the frozen values. ### You can probe the reference directly The pinned toolchain image is in CT 100 and OpenSCAD will echo whatever you ask it. This turned F-034 from a guess into a measurement in about four exchanges, and it will settle any later disagreement the same way. Mount the tree **read-only** and keep the probe on a separate writable mount. Docker runs as root and a read-write mount against a `mechcomp`-owned tree is the F-033 hazard. Note that a nested single-file mount inside a read-only mount fails with `EXIT=125` — put the probe in its own directory instead. ```bash pct exec 100 -- bash -c 'mkdir -p /tmp/probe && cat > /tmp/probe/p.scad << "EOF" include $fn = 48; g = sb_geo(15.875, 0.508, 1, 0.25, 1.20, 1.20, 1.20, 1.20); echo(str("PROBE=", sb_area([sb_sleeve_path(sb_member(0,0,0), g)]))); EOF docker run --rm -v /var/www/mechcomp:/repo:ro -v /tmp/probe:/probe -w /probe \ mechcomp/reference-toolchain:8.0.0 \ openscad -o /tmp/o.stl --export-format=asciistl p.scad 2>&1 | grep -E "PROBE|ERROR"' ``` `EXIT=1` is normal — STL export fails on 2D geometry. The echoes still arrive. The harvester itself exports to `/dev/null` for exactly this reason: the export only forces evaluation. ### The oracle's parameter defaults `strap-beam-3x.scad` lines 65–135. Notably `y_junction_round_mm = 1.50`, `three_fin_junction_round_mm = 2.00`, `ring_corner_radius_mm = 2.00`, `facets = 48`, `material_density_g_cm3 = 1.24`. All 123 cases run at `facets = 48`; nothing overrides it. ### Environment facts Shapely 2.1.2 on GEOS 3.13.1. Shapely is in `requirements-base.txt`, the 2D path's own dependency set. **No CAD kernel is installed in CT 100** — `cadquery`, `OCP` and `build123d` are all absent, though `requirements-cad.txt` says it is installed by default. That is the strictest possible environment for the 2D path and will matter when STL and STEP export starts. Git identity is now set `--local` in the clone as `TheRON `, matching the two most recent commits. It must stay local: `mechcomp`'s home **is** the working tree (F-029), so `--global` would write into the repository. --- ## 4. Verified against the reference Three-Fin at the oracle's defaults reproduces **every recorded value exactly**, including `SECTION_AREA_MM2 146.787`, `VOLUME_MM3 14678.7` and `MASS_G 18.2016`. That case exercises butt joints, `fillet_junctions`, `fillet_pair`, `fillet_concave`, `round_corners`, the derived bore and the fin solver. Y reproduces every value except section area and its two derivatives. Exact across both: `SPOKE_RADIUS_MM 9.1713`, `SPOKE_WEB_MM 1.2`, `FIN_CORE_SIDE_MM 13.4028`, `FIN_SETBACK_MM 3.77783`, `FIN_JUNCTION_WEB_MM 2.29919`, `FIN_BORE_SIDE_MM 7.5`, `RING_CORNER_R_MAX_MM 2.87663`, the ring edge vector, and both envelope dimensions on both profiles. **The numerical machinery is right.** Where the port disagrees, it is F-034. --- ## 5. F-034, and the decision it will force Read the entry in full. The short version: Arc segment counts are `ceil((90 - half_angle)/180 * $fn)`, and that expression is frequently an exact integer — a 60° half-angle at `$fn = 48` gives exactly 8. Floating point delivers it as `8.000000000000004` or `7.999999999999998` depending on how the corner was reached. The half-angles come from the merged polygon, whose vertices come from the boolean kernel, and BOSL2's clipper and GEOS disagree in the last bit. **Do not try to fix this.** It was tried and reverted. Rounding the count before the ceiling makes the three symmetric pairs identical and fixes Y exactly — and breaks Three-Fin, which had been matching to the digit, because Three-Fin has the same asymmetry and **the oracle records it**. Two consequences: **Some recorded values encode float noise rather than geometry.** A port that is geometrically more correct than the reference will fail those cases. **The tolerance model may need revisiting, and that is CIVICVS's call.** Any case affected fails on `VOLUME_MM3` and `MASS_G` long before it fails on `SECTION_AREA_MM2`, because volume is checked at 1e-4 while being area times 100 mm. **Do not raise this until `build()` exists and all 123 cases have run.** The number of affected cases is unknown and is the only thing that should drive the decision. It may be one case. Bring the count and a recommendation, then stop. --- ## 6. How this session worked, and what to keep **Read the source, do not recall it.** BOSL2 was fetched at the pinned commit and read directly — `round_corners`, `_circlecorner`, `arc`, `segs`, `deduplicate`, `is_collinear`, `vector_angle`. Every one of those had a detail that mattered and that recollection would have got subtly wrong. **Mutation-test every suite before landing it.** Break the code deliberately and confirm the tests notice. This found real gaps five times in six slices: - nothing exercised BOSL2's three-point floor on blunt corners - nothing distinguished on-boundary from outside in the nesting probe - the area guard alone accepts a bore that has turned inside out - the ring fit really does have a spurious lower branch below scale 1 - nothing asserted that `clean_region` removed anything It also caught one malformed mutation of mine — cutting the cavities twice is idempotent — which is worth knowing: a surviving mutation is sometimes a bad mutation, not a test gap. Check which before writing a test. **Deliver by upload, not by paste.** `PROCESS.md` §3. A sixty-line heredoc with em-dashes and nested code fences was mangled by the browser terminal. Everything since went as a tarball with a stated checksum, file list, and what it overwrites. That has not failed once. **One command group per message, and wait.** Every command group in this session was read-only first where the answer was not certain. --- ## 7. State Gitea `main` at `86a47c3`. CT 100 clean and matching. Suite: **230 passed, 236 skipped**. ``` 86a47c3 rounding: record F-034, arc segment counts tip on the last bit 8d79431 geom: port sb-core and sb-profiles, the N-generic arrangements b101fe6 geom: port sb-report, validation and report formatting ebf02d6 geom: port sb-join, the junction and envelope strategies 38ea024 geom: Shapely-backed region layer 545eee7 geom: port BOSL2 round_corners and path cleanup dfd02a4 geom: port the pure-geometry half of sb-geom b67cc12 verify.sh: reach the restore on the diff branch 1d3eed0 Handover push ``` The toolchain gate passed at the start of this session: all 123 cases regenerate byte-identically inside the pinned image, 113 accepted and 10 rejected, the only diff being `frozen` and the hash containing it. The oracle is a stable target. `verify.sh --full` exits 1 by design on any diff, and the `frozen` date guarantees one. That is a pass, not a fault. Its restore path was broken and is now fixed but **still unexercised** — see F-033. The next `--full` run proves it.