docs: handoff for the 19 AUG session

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.
This commit is contained in:
2026-08-19 08:01:06 -05:00
parent 86a47c3c06
commit 009fcce61c
+237
View File
@@ -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 </repo/legacy/openscad/lib/sb-core.scad>
$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
<webmaster@kane-il.us>`, 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.