docs: handoff rewritten for the post-port state; F-034 measured

HANDOFF.md rewritten in place, as it is meant to be. The port is complete,
so the document now describes that state rather than the work leading to it.

Most important change for whoever reads it next: the suite is 436 passed,
30 failed, and section 3 says plainly that the 30 are expected and a red
`make test` is not a broken port. Without that line the next assistant
spends its opening exchange rediscovering what is already known.

Also added: the 0.01 mm accuracy criterion as a settled decision; that
defaults are per-family and differ (ring_corner_radius_mm is 2.00 in 3x
and 1.25 in 4x); that check declaration order is the reporting order; that
params carries only a case's overrides; and pointers to PRECISION.md for
what the compiler does not do.

F-034 rewritten around measurement rather than estimate. The original Y
evidence is preserved verbatim -- it was specific and hard-won. What is
new:

  - the same mechanism at 90 degree ring corners, where the expression is
    exactly 12 rather than exactly 8. Rectangle: reference envelope 50
    vertices, port 48.
  - which side is noisy, which was previously unstated and turns out to
    matter. Instrumented at %.17g the port prints half=45 raw=12 ceil=12
    on every call. It lands on the integer deterministically; the
    reference does not. A guard cannot make the port match noise it does
    not have, so there is nothing left to try on this side.
  - the scale: 30 of 113 accepted cases, 83 exact, worst relative error
    2.24e-05, confined to SECTION_AREA_MM2 and its two derivatives.
  - the physical magnitude: chord deviation is r*(1-cos 3.75deg), so
    0.0027 mm at r=1.25 and 0.0043 mm at r=2.00. The two implementations
    differ from each other by at most ~0.4 um. Two orders inside the
    0.01 mm criterion.
  - the constraint on any fix: the tolerance block is inside the hashed
    oracle document, so the change belongs in test_oracle.py.

Status stays Open. The decision is CIVICVS's and has not been made.
This commit is contained in:
2026-08-20 06:54:11 -05:00
parent 114189c0cb
commit b255ebbab9
2 changed files with 254 additions and 111 deletions
+91 -21
View File
@@ -774,11 +774,15 @@ it with `git status` whenever the question is *which* oracle is present.
### F-034 — arc segment counts land on exact integers and tip on the last bit
CT 100. Shapely port, `round_corners`.
**Observed:** the Y profile builds a section area of 135.572973 against a
recorded 135.574 — out by 0.001027, just past the 1e-3 area tolerance — while
every other recorded value for that case matches exactly: envelope, solved spoke
radius, minimum wall, channel count. Three-Fin matches on every value including
area.
**Measured 2026-08-20. Open, awaiting a tolerance decision from CIVICVS.**
The scale of the effect is now known and the port's side is settled: **the port
is exact and the reference is noisy.** Nothing here can be corrected in the port.
**Observed, first evidence (60 degree half-angles).** The Y profile builds a
section area of 135.572973 against a recorded 135.574 — out by 0.001027, just
past the 1e-3 area tolerance — while every other recorded value for that case
matches exactly: envelope, solved spoke radius, minimum wall, channel count.
Three-Fin matches on every value including area.
Isolated by probing the reference directly inside the pinned toolchain image.
The hull cap is identical to nine figures. The bare union before filleting is
@@ -788,18 +792,39 @@ The whole difference appears when the three filleted pairs are combined.
The three pairs are related by 120 degree symmetry and must be identical. They
come back as `[125.699057, 125.698029, 125.699057]`.
**Observed, second evidence (90 degree corners).** The ring profiles hit the same
mechanism at a different angle. For Rectangle at defaults, the reference builds
an outer envelope of **50 vertices and the port builds 48** — `13+13+12+12`
against `12x4`. `RING_SCALE`, `RING_EDGES_MM`, `RING_CORNER_WEB_MM` and
`RING_CORNER_R_MAX_MM` all agree to six figures, so the fitted polygon is
identical and the divergence is entirely in the rounding.
**Cause:** **Proven.** `_circlecorner` sets the arc segment count to
`max(3, ceil((90 - half_angle)/180 * $fn))`. At a 60 degree half-angle with
`$fn = 48` that expression is mathematically exactly 8. Floating point delivers
it as `8.000000000000004` on one corner and `7.999999999999998` on the other
two, so the ceiling gives 9 on one and 8 on the others — one extra segment,
slightly less enclosed area.
`max(3, ceil((90 - half_angle)/180 * $fn))`. That expression is frequently a
mathematically exact integer:
| corner | half-angle | expression at `$fn = 48` |
|---|---|---|
| spoke / fin junction | 60 deg | exactly 8 |
| ring corner | 45 deg | exactly 12 |
A ceiling on an exact integer is a knife edge. Floating point delivers 8 as
`8.000000000000004` on one corner and `7.999999999999998` on the others, so the
ceiling gives 9 on one and 8 on the rest — one extra segment, slightly less
enclosed area.
The half-angle is computed from the merged polygon's vertices, and those come
from the boolean kernel. BOSL2's clipper and GEOS agree to well within any
meaningful tolerance but not to the last bit, so they tip these ceilings
differently.
**Which side is noisy is now measured, and it is not the port.** Instrumenting
`_circlecorner` and printing at `%.17g` gives `half=45 raw=12 ceil=12` on every
call, for both Square and Rectangle — the port lands dead on the integer,
deterministically. The reference's arithmetic lands a hair under 45 degrees at
some corners, pushing the value fractionally above 12 and the ceiling to 13.
Square passes because the reference happens to land on 12 there too.
**Correction: none. Reproduced unguarded, because the reference is unguarded.**
Rounding the count before the ceiling was implemented and reverted. It makes all
@@ -812,7 +837,38 @@ BOSL2 guards this identical hazard inside `segs()` with a `2e-15` subtraction
but not at this call site. Adding the guard is better engineering and produces a
different answer from the reference, which is what matters here.
**Consequence:** Three things.
**There is nothing left to try on the port's side.** The port is already exact;
a guard cannot make it match noise it does not have.
**Scale of the effect.** Measured against all 123 cases once `build()` existed:
- **30 of 113 accepted cases affected. 83 are exact.**
- **Only three keys ever breach:** `SECTION_AREA_MM2` (29 cases), `VOLUME_MM3`
(30), `MASS_G` (22). Volume is area times 100 mm and mass is volume times
density over 1000, so each case carries **one** discrepancy reported three
times.
- **Worst relative error 2.24e-05.**
- By profile: Three-Fin 9, Y 7, A Frame 5, Rectangle 5, T 1, Four-Fin 1. None on
Equilateral Triangle, General Triangle, Square, Diamond or Cross.
- **Every dimensional quantity passes**, in all 123 cases: `ENVELOPE_X_MM`,
`ENVELOPE_Y_MM`, `MIN_WALL_ACTUAL_MM`, and every profile extra (`AF_*`,
`FIN_*`, `SPOKE_*`, `RING_*`, `T_*`). Every count is exact. All ten rejections
fire correctly.
**Physical magnitude.** At `$fn = 48` a chord deviates from its true arc by
`r(1 - cos 3.75 deg)` = `r x 2.1413e-3`:
| corner radius | deviation from the true arc |
|---|---|
| 1.25 mm (4x ring corners) | 0.0027 mm |
| 2.00 mm (3x ring corners) | 0.0043 mm |
The port and the reference differ from **each other** by at most about 0.4 um,
and both sit within 0.0043 mm of the exact arc. Against the project's 0.01 mm
accuracy criterion that is two orders of margin. **The geometry is not in
question; only the comparison is.**
**Consequence:** three things, the third now actionable.
**Some recorded values encode float noise, not geometry.** Three-Fin's area
reflects a spurious extra segment on one of three symmetric corners. A port that
@@ -822,17 +878,31 @@ is geometrically more correct than the reference will fail that case.
general.** Not for want of care in the port. Wherever a corner angle lands on an
exact segment boundary, the result is decided by the kernel's last bit.
**The tolerance model may need revisiting, and that is CIVICVS's call.**
**The tolerance model needs revisiting, and that is CIVICVS's call.**
`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. It is section area
times a 100 mm length, so an area difference of 1e-3 becomes a volume difference
of 1e-1 — a thousand times the tolerance it is checked against. `MASS_G` is
derived the same way. Any case affected by this failure mode fails on volume and
mass long before it fails on area.
tolerance of 1e-4 rather than the areas tolerance of 1e-3 — against a magnitude
near 20000, which demands 5e-9 relative agreement from discretised geometry.
`3x/Y/steel0.79` breaches on `VOLUME_MM3` alone while its area passes: one
discrepancy, judged by two wildly different standards by accident of key naming.
Do not act on this until `build()` exists and all 123 cases can be run. The
number of affected cases is unknown and is the only thing that should drive the
decision.
**Constraint on any fix: do not edit `tolerance` in the oracle JSON.** It sits
inside the hashed document — `test_integrity_hash` covers everything except
`fixtures_sha256` — so editing it breaks that test by design. The change belongs
in `test_oracle.py`, which is not hashed. Options, in the order recommended:
1. **Scale-aware bounds for the three discretisation-limited keys**, derived from
the 0.01 mm criterion and the section perimeter. Most faithful to where the
error originates.
2. **A relative floor** — pass if within the absolute tolerance *or* within about
1e-4 relative. Simplest. Loosens `SECTION_AREA_MM2` from 0.001 mm^2 to about
0.02 mm^2, a real but bounded cost.
3. **Accept 30 known failures** and mark them expected. Honest, but `make test`
is never green and a real regression would hide among them.
Whichever is chosen, the checks that catch structural error — `SECTION_PARTS`,
`STRAP_CHANNELS`, `MIN_WALL_ACTUAL_MM`, the envelope dimensions — stay exact. A
genuine geometry fault runs 1e-2 relative or worse and would still be caught with
three orders of margin.
---
@@ -903,7 +973,7 @@ running every repository operation as `mechcomp`. All are now stated as facts in
| F-031 | **Corrected** 2026-08-18. Root cause of `ct-baseline.sh`. |
| F-032 | **Closed** 2026-08-18. No correction required; encoded in `ct-baseline.sh`. |
| F-033 | **Corrected** 2026-08-19. Restore path unreachable under `set -e`. |
| F-034 | **Open** 2026-08-19. Reproduced unguarded; affects an unknown number of oracle cases. |
| F-034 | **Open** 2026-08-20. Measured: the port is exact, the reference is noisy. 30 of 113 accepted cases, worst relative error 2.24e-05, confined to `SECTION_AREA_MM2` and its two derivatives. Nothing correctable in the port; awaiting a CIVICVS decision on the tolerance model. |
| F-035 | **Corrected** 2026-08-19. Use `runuser`, never `su`; `mechcomp` is `nologin`. |
Everything else is closed with a proven cause and a proven correction.