diff --git a/docs/HANDOFF-2026-08-19.md b/docs/HANDOFF-2026-08-19.md new file mode 100644 index 0000000..c8073ac --- /dev/null +++ b/docs/HANDOFF-2026-08-19.md @@ -0,0 +1,237 @@ +# 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.