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 ### F-034 — arc segment counts land on exact integers and tip on the last bit
CT 100. Shapely port, `round_corners`. CT 100. Shapely port, `round_corners`.
**Observed:** the Y profile builds a section area of 135.572973 against a **Measured 2026-08-20. Open, awaiting a tolerance decision from CIVICVS.**
recorded 135.574 — out by 0.001027, just past the 1e-3 area tolerance — while The scale of the effect is now known and the port's side is settled: **the port
every other recorded value for that case matches exactly: envelope, solved spoke is exact and the reference is noisy.** Nothing here can be corrected in the port.
radius, minimum wall, channel count. Three-Fin matches on every value including
area. **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. 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 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 The three pairs are related by 120 degree symmetry and must be identical. They
come back as `[125.699057, 125.698029, 125.699057]`. 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 **Cause:** **Proven.** `_circlecorner` sets the arc segment count to
`max(3, ceil((90 - half_angle)/180 * $fn))`. At a 60 degree half-angle with `max(3, ceil((90 - half_angle)/180 * $fn))`. That expression is frequently a
`$fn = 48` that expression is mathematically exactly 8. Floating point delivers mathematically exact integer:
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, | corner | half-angle | expression at `$fn = 48` |
slightly less enclosed area. |---|---|---|
| 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 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 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 meaningful tolerance but not to the last bit, so they tip these ceilings
differently. 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.** **Correction: none. Reproduced unguarded, because the reference is unguarded.**
Rounding the count before the ceiling was implemented and reverted. It makes all 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 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. 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 **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 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 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. 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 `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 tolerance of 1e-4 rather than the areas tolerance of 1e-3 — against a magnitude
times a 100 mm length, so an area difference of 1e-3 becomes a volume difference near 20000, which demands 5e-9 relative agreement from discretised geometry.
of 1e-1 — a thousand times the tolerance it is checked against. `MASS_G` is `3x/Y/steel0.79` breaches on `VOLUME_MM3` alone while its area passes: one
derived the same way. Any case affected by this failure mode fails on volume and discrepancy, judged by two wildly different standards by accident of key naming.
mass long before it fails on area.
Do not act on this until `build()` exists and all 123 cases can be run. The **Constraint on any fix: do not edit `tolerance` in the oracle JSON.** It sits
number of affected cases is unknown and is the only thing that should drive the inside the hashed document — `test_integrity_hash` covers everything except
decision. `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-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-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-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`. | | 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. Everything else is closed with a proven cause and a proven correction.
+163 -90
View File
@@ -6,7 +6,7 @@ It is the only handoff you need to read. Dated handoffs in `docs/archive/` are
historical and are not required reading — do not diff them against this to work historical and are not required reading — do not diff them against this to work
out what is true. If something here is wrong, correct it here. out what is true. If something here is wrong, correct it here.
Last updated 2026-08-19 at commit `009fcce`. Last updated 2026-08-20 at commit `114189c`.
--- ---
@@ -90,6 +90,9 @@ delivered since as a tarball — with a stated checksum, file list, and what it
overwrites — has worked without exception. State what the archive contains, where overwrites — has worked without exception. State what the archive contains, where
it expands, and what it overwrites, every time. it expands, and what it overwrites, every time.
When a delivery supersedes an earlier one, **ship every file in the set**, not
just the changed one, so the resulting state is unambiguous.
--- ---
## 3. Where things stand ## 3. Where things stand
@@ -116,18 +119,21 @@ terminate rather than recur.
**Settled decisions. Do not reopen any of these:** **Settled decisions. Do not reopen any of these:**
- Webmin is the operator's only remote access. Questioning it wasted a session. - Webmin is the operator's only remote access. Questioning it wasted a session.
- Backup is deliberately postponed. A backup of an unverified configuration - Backup is deliberately postponed. Entry condition: `ct-baseline.sh` exits 0.
restores the confusion along with the data. Entry condition: Do not raise it again.
`ct-baseline.sh` exits 0. Do not raise it again.
- `4x` is the end of the N-strap family. - `4x` is the end of the N-strap family.
- The `--full` toolchain gate's Docker root-ownership hazard is understood and - The `--full` toolchain gate's Docker root-ownership hazard is understood and
handled by mounting read-only. See §6. handled by mounting read-only. See §6.
- **Accuracy criterion: 0.01 mm over the entire set.** Set by CIVICVS on 20 AUG.
This is the standard the software is held to, and it is met with two orders of
margin on every dimensional quantity. See `docs/PRECISION.md`.
### The port — shared layer complete ### The port — COMPLETE
Gitea `main` at `009fcce`. CT 100 clean and matching. Suite: **230 passed, 236 Gitea `main` at `114189c`. CT 100 clean and matching.
skipped**. The 236 are the oracle acceptance tests; they skip because
`mechcomp.profiles.build` does not exist yet. **Suite: 436 passed, 30 failed. The 30 failures are expected.** Do not treat a
red `make test` as a broken port — read §7 before doing anything about it.
| Module | Ported from | Contents | | Module | Ported from | Contents |
|---|---|---| |---|---|---|
@@ -138,6 +144,10 @@ skipped**. The 236 are the oracle acceptance tests; they skip because
| `geom/report.py` | `sb-report.scad` | Checks-as-values, metrics, five universal checks, `ProfileRejected`, report formatting | | `geom/report.py` | `sb-report.scad` | Checks-as-values, metrics, five universal checks, `ProfileRejected`, report formatting |
| `geom/core.py` | `sb-core.scad` | PROFILE record, failure representation, centred assembly | | `geom/core.py` | `sb-core.scad` | PROFILE record, failure representation, centred assembly |
| `geom/arrangements.py` | `sb-profiles.scad` | Ring, spoke and fin arrangements, all N-generic | | `geom/arrangements.py` | `sb-profiles.scad` | Ring, spoke and fin arrangements, all N-generic |
| `profiles/_common.py` | both generators | Assembly pipeline and the eight shared base checks |
| `profiles/four_x.py` | `strap-beam-4x.scad` | Five profiles, defaults, 4x base checks |
| `profiles/three_x.py` | `strap-beam-3x.scad` | Six profiles, defaults, 3x base checks |
| `profiles/__init__.py` | — | `build()`, `ProfileRejected`, family registry |
`make deps` is complete in CT 100 and **must not be re-run**. `make deps` is complete in CT 100 and **must not be re-run**.
@@ -145,29 +155,53 @@ skipped**. The 236 are the oracle acceptance tests; they skip because
## 4. What to do next ## 4. What to do next
Port the eleven catalogue profiles and the `build()` entry point. **Nothing is blocked. Pick up §7 first if the operator wants it closed; otherwise
the port is done and the next work is new capability.**
**Read `strap-beam-4x.scad` before `strap-beam-3x.scad`.** The 4x file is Immediate, small, and already scoped:
smaller, has five profiles, no rejection cases, and all five are direct calls
into the three arrangements that are already ported and verified. If those five
build and match the oracle, the port is confirmed end to end before you touch the
3x file, where all ten rejections and the bespoke profiles live.
**The public entry point is `mechcomp.profiles`, not `mechcomp.geom`.** 1. **F-034 tolerance decision** (§7). Evidence is gathered; the decision is
`conftest.py` does `importorskip("mechcomp.profiles")` and requires a `build` CIVICVS's. Do not implement before he chooses.
attribute. The geometry lives under `mechcomp.geom`; `build(family, profile, 2. **Rewrite F-034 in `FAILURES.md`** around the measurements below. The entry
params) -> Result` and `ProfileRejected` must be exported from still describes it as an unquantified hazard; it is now measured.
`mechcomp.profiles`. Params use the OpenSCAD parameter names unchanged.
| Family | Profiles | Rejections | After that, the roadmap items — read `ROADMAP.md` before starting any:
|---|---|---|
| 3x | Equilateral Triangle, General Triangle, A Frame, Y, T, Three-Fin | 10 | - STL and STEP export. No CAD kernel is installed in CT 100 (§5).
| 4x | Square, Rectangle, Diamond, Cross, Four-Fin | 0 | - Dihedral parameterisation. If two panels meet at 137°, none of the eleven
profiles gives you a member for it. This is the highest-value gap.
- Nodes (non-prismatic) and panels (sheet). No representation exists for either.
--- ---
## 5. Facts established by porting ## 5. Facts established by porting
### The public entry point is `mechcomp.profiles`, not `mechcomp.geom`
`conftest.py` does `importorskip("mechcomp.profiles")` and requires a `build`
attribute. `build(family, profile, params) -> Result` and `ProfileRejected` are
exported from `mechcomp.profiles`. Params use the OpenSCAD parameter names
unchanged.
**`params` carries only a case's overrides.** Everything else comes from the
family's declared defaults, which is why those defaults live in the port rather
than the test harness.
### Defaults are per-family, not shared
`ring_corner_radius_mm` is **2.00 in 3x** and **1.25 in 4x**. The two generators
declare their own parameter blocks and they are not identical. Do not assume a
value read from one file applies to the other. 4x also declares
`rectangle_aspect`, which 3x has no equivalent of, and its base-check list is
correspondingly one entry longer.
### Check order is the reporting order
The reference reports the **first** failing check, not an aggregate. Base checks
run before profile checks, and both run before any geometry is measured — a
builder that failed returns an empty profile, and `centred()` on an empty profile
has no members to measure.
### Report values are rounded to six significant figures ### Report values are rounded to six significant figures
The oracle records what OpenSCAD's `echo` printed — C's `%g` at default The oracle records what OpenSCAD's `echo` printed — C's `%g` at default
@@ -177,63 +211,46 @@ handles vectors, which reach the oracle as strings like
Load-bearing, not cosmetic. `VOLUME_MM3` ends in `_MM3`, so `test_oracle.py` Load-bearing, not cosmetic. `VOLUME_MM3` ends in `_MM3`, so `test_oracle.py`
compares it at the **lengths** tolerance of 1e-4, not the areas tolerance of compares it at the **lengths** tolerance of 1e-4, not the areas tolerance of
1e-3. Volume is area times a 100 mm length, so an unrounded port reporting 1e-3.
13557.402 against a recorded 13557.4 fails by twenty times the tolerance while
being geometrically correct.
Verified: across all 113 accepted cases, recorded volume equals rounded area
times length to within 3.6e-12.
### Angles are degrees; the arbitrary constants are contract ### Angles are degrees; the arbitrary constants are contract
OpenSCAD trigonometry is in degrees. The port keeps degrees throughout with OpenSCAD trigonometry is in degrees. The port keeps degrees throughout with
explicit `cos_d`/`sin_d`/`tan_d` so every expression matches its source line. The explicit `cos_d`/`sin_d`/`tan_d` 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 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 cutoffs and the 1e9 sentinel are reproduced exactly. They produced the frozen
improve on; they produced the frozen values. values.
### Environment ### Environment
Shapely 2.1.2 on GEOS 3.13.1 — keep these in step, boolean results on Shapely 2.1.2 on GEOS 3.13.1 — keep these in step, boolean results on
near-degenerate geometry can shift between GEOS releases. numpy 2.4.6. Shapely near-degenerate geometry can shift between GEOS releases. numpy 2.4.6. Shapely is
is in `requirements-base.txt`, the 2D path's own dependency set. in `requirements-base.txt`, the 2D path's own dependency set.
**No CAD kernel is installed in CT 100.** `cadquery`, `OCP` and `build123d` are **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 all absent, though `requirements-cad.txt` says it is installed by default. That
is the strictest environment for developing the 2D path and will matter when STL is the strictest environment for developing the 2D path and will matter when STL
and STEP export begins. and STEP export begins.
### Oracle parameter defaults
`strap-beam-3x.scad` lines 65–135. `facets = 48` and no case overrides it.
`fit_clearance_mm = 0.25`, all four wall thicknesses `1.20`,
`material_density_g_cm3 = 1.24`, `y_junction_round_mm = 1.50`,
`three_fin_junction_round_mm = 2.00`, `ring_corner_radius_mm = 2.00`.
### What is verified against the reference ### What is verified against the reference
Three-Fin reproduces **every recorded value exactly**, including **Every dimensional quantity is exact to 1e-4 mm across all 123 cases, with no
`SECTION_AREA_MM2 146.787`, `VOLUME_MM3 14678.7`, `MASS_G 18.2016`. That case exceptions.** `ENVELOPE_X_MM`, `ENVELOPE_Y_MM`, `MIN_WALL_ACTUAL_MM`, and every
exercises butt joints, `fillet_junctions`, `fillet_pair`, `fillet_concave`, profile extra: `AF_*`, `FIN_*`, `SPOKE_*`, `RING_*`, `T_*`. Every count is exact.
`round_corners`, the derived bore and the fin solver.
Y reproduces every value except section area and its two derivatives — see **All ten rejections fire correctly**, including the bespoke A Frame and T paths,
F-034. which were written from the join primitives rather than from a shared
arrangement. That is strong evidence those two are structurally right.
Exact across both: `SPOKE_RADIUS_MM 9.1713`, `FIN_CORE_SIDE_MM 13.4028`, Only `SECTION_AREA_MM2` and its two derivatives ever disagree. See §7.
`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.
**The numerical machinery is right.**
--- ---
## 6. Probing the reference directly ## 6. Probing the reference directly
The pinned toolchain image is in CT 100 and OpenSCAD will echo whatever you ask 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 four exchanges, and it it. This settled F-034 in four exchanges and will settle any later disagreement
will settle any later disagreement the same way. Prefer it to reasoning. the same way. **Prefer it to reasoning.**
Mount the tree **read-only** and keep the probe on a separate writable mount — 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 Docker runs as root, and a read-write mount against a `mechcomp`-owned tree is
@@ -253,40 +270,80 @@ docker run --rm -v /var/www/mechcomp:/repo:ro -v /tmp/probe:/probe -w /probe \
``` ```
`EXIT=1` is normal — STL export fails on 2D geometry, the echoes still arrive. `EXIT=1` is normal — STL export fails on 2D geometry, the echoes still arrive.
The harvester exports to `/dev/null` for the same reason: the export only forces
evaluation.
**The oracle itself is stable.** The gate was run on 19 AUG: all 123 cases **The oracle itself is stable.** The gate was run on 19 AUG: all 123 cases
regenerate byte-identically inside the pinned image, 113 accepted and 10 regenerate byte-identically inside the pinned image, 113 accepted and 10
rejected, the only diff being `frozen` and the hash containing it. rejected, the only diff being `frozen` and the hash containing it.
`verify.sh --full` exits 1 on any diff by design and the `frozen` date guarantees `verify.sh --full`'s restore path was broken, is now fixed, and is **still
one — that is a pass, not a fault. Its restore path was broken, is now fixed, and unexercised** (F-033).
is **still unexercised** (F-033). The next `--full` run proves it.
**Instrumenting the port is equally cheap.** Monkeypatching
`mechcomp.geom.rounding._circlecorner` to print its half-angle and segment count
turned the F-034 diagnosis from inference into measurement in one command. The
module calls it through the module global, so patching the attribute works.
--- ---
## 7. F-034 — open, and it will force a decision ## 7. F-034 — measured, and the decision is ready
Read the entry in full. Short version: arc segment counts are **The port is exact. The reference is noisy.** That is the opposite of what the
`ceil((90 - half_angle)/180 * $fn)`, and that is frequently an exact integer — a entry previously implied, and it changes what can be done about it.
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 `_circlecorner` computes `raw = (90 - angle)/180 * segs(r, None, fn)` and takes
the ceiling fixes Y exactly and breaks Three-Fin, which had been matching to the `ceil(raw)`. At `$fn = 48` a 90° corner has half-angle 45, and `(90-45)/180*48`
digit, because Three-Fin has the same asymmetry and **the oracle records it**. is **exactly 12**. A ceiling on an exact integer is a knife edge.
Consequences: some recorded values encode float noise rather than geometry, so a Measured: the port prints `half=45 raw=12 ceil=12` at `%.17g` — landing dead on
port that is geometrically more correct than the reference will fail those cases. the integer, every call, both families. The reference's arithmetic lands a hair
And any affected case fails on `VOLUME_MM3` and `MASS_G` long before it fails on under 45° at some corners, pushing `raw` fractionally above 12 and the ceiling to
`SECTION_AREA_MM2`. 13. On Rectangle: **reference envelope 50 vertices, port 48** (`13+13+12+12`
against `12×4`).
**Do not raise the tolerance question until `build()` exists and all 123 cases **Do not try to fix this.** Rounding before the ceiling was tried and reverted —
have run.** The number of affected cases is unknown and is the only thing that it fixes Y exactly and breaks Three-Fin, because Three-Fin has the same asymmetry
should drive the decision. It may be one case. Bring the count and a and the oracle records it. There is nothing to correct on the port's side.
recommendation, then stop.
### Scale of the effect
30 of 113 accepted cases affected. 83 exact. Only three keys ever breach:
`SECTION_AREA_MM2` (29), `VOLUME_MM3` (30), `MASS_G` (22) — and volume is area
×100, mass is volume ×density/1000, so each case has **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, General Triangle, Square, Diamond or Cross.
### Why the test fails when the geometry is fine
At `$fn = 48` a chord deviates from its true arc by `r(1 − cos 3.75°)` =
`r × 2.1413e-3`: **2.68 µm at r=1.25, 4.28 µm at r=2.00.** The port and the
reference differ from *each other* by at most ~0.4 µm. Against the 0.01 mm
criterion (§3) that is two orders of margin.
The tests fail because `VOLUME_MM3` is compared at 1e-4 **absolute** against a
magnitude near 20,000 — demanding 5e-9 relative agreement from discretised
geometry. `3x/Y/steel0.79` breaches on `VOLUME_MM3` alone while its area passes:
the same discrepancy, judged by two wildly different standards by accident of key
naming.
### The options, and the constraint
**Do not edit `tolerance` in the oracle JSON.** It is inside the hashed document;
`test_integrity_hash` covers everything but `fixtures_sha256`. Editing it breaks
that test by design.
The change belongs in `test_oracle.py`, which is not hashed:
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. Recommended.
2. **A relative floor** — pass if within absolute tolerance *or* ~1e-4 relative.
Simplest; loosens `SECTION_AREA_MM2` from 0.001 to ~0.02 mm².
3. **Accept 30 known failures.** Honest but `make test` is never green and a real
regression hides among them.
**CIVICVS decides.** The evidence is above; bring him the recommendation and
stop.
--- ---
@@ -295,18 +352,24 @@ recommendation, then stop.
**Read the pinned source; do not recall it.** BOSL2 was fetched at **Read the pinned source; do not recall it.** BOSL2 was fetched at
`92d697c2856de2fed93a33e858068589cefc2898` and read directly. Every function `92d697c2856de2fed93a33e858068589cefc2898` and read directly. Every function
examined had a detail that mattered and that recollection would have got subtly examined had a detail that mattered and that recollection would have got subtly
wrong. wrong. The same applied to the generators: reading `sb-profiles.scad` before
writing `four_x.py` confirmed the call signatures rather than inferring them from
call sites.
**Check the invocation before concluding anything about state.** A `su` that
failed identically on two commands read as a repository problem and was an
invocation problem. That is the F-027 pattern and it cost a session's opening
exchange.
**Verify your own arithmetic before shipping it.** A claim in `PRECISION.md` that
a 50 mm radius needs `facets ≈ 460` was wrong — deviation falls with the *square*
of the segment angle, so the count grows with the square root of radius and the
answer is 158. A three-line script caught it.
**Mutation-test every suite before landing it.** Break the code deliberately and **Mutation-test every suite before landing it.** Break the code deliberately and
confirm the tests notice. This found real gaps in five of six slices: the confirm the tests notice. This found real gaps in five of six slices. It also
three-point floor on blunt corners, on-boundary versus outside in the nesting caught a malformed mutation of mine — cutting the cavities twice is idempotent.
probe, a bore that has turned inside out carrying real area, the ring fit's **A surviving mutation is sometimes a bad mutation, not a test gap.**
spurious lower branch below scale 1, and nothing asserting that `clean_region`
removed anything.
It also caught a malformed mutation of mine — cutting the cavities twice is
idempotent. **A surviving mutation is sometimes a bad mutation, not a test gap.**
Check which before writing a test.
**Read-only before write.** Every command group where the answer was not certain **Read-only before write.** Every command group where the answer was not certain
established the facts first. established the facts first.
@@ -328,14 +391,17 @@ physical claims, never verdicts. Measure and attest; never adjudicate.
Three artifact classes: **members** (prismatic, exist — the eleven profiles), Three artifact classes: **members** (prismatic, exist — the eleven profiles),
**nodes** (non-prismatic, no representation yet), **panels** (sheet, none yet). **nodes** (non-prismatic, no representation yet), **panels** (sheet, none yet).
The next gap in priority is dihedral parameterisation: if two panels meet at
137°, none of the eleven profiles gives you a member for it.
**The output must eventually be sealed manifolds and printable STL.** The 2D **The output must eventually be sealed manifolds and printable STL.** The 2D
section is where manifold validity is decided, not downstream in the CAD kernel — section is where manifold validity is decided, not downstream in the CAD kernel —
`is_region_simple` is already a build-blocking check, and a self-touching outline `is_region_simple` is already a build-blocking check, and a self-touching outline
extrudes into something untessellatable. extrudes into something untessellatable.
**`docs/PRECISION.md` states what this compiler does not do**: no assembly layer,
no structural analysis of any kind, prismatic shapes only, no toolpaths. That
document is scope-locked to additive and subtractive manufacturing. **Requests to
widen it should be refused, not accommodated** — see its §10.
--- ---
## 10. Working with this operator ## 10. Working with this operator
@@ -354,6 +420,10 @@ autonomous regardless.
When he says he does not understand something, the writing was unclear. Rewrite When he says he does not understand something, the writing was unclear. Rewrite
it shorter; do not explain it again at greater length. it shorter; do not explain it again at greater length.
**He watches for scope creep and treats it as the primary risk** — "the greatest
enemy of software is not bugs, it's feature-creep". Documents and code are both
held to it. When in doubt, narrow.
--- ---
## 11. Open questions, none blocking ## 11. Open questions, none blocking
@@ -364,7 +434,7 @@ it shorter; do not explain it again at greater length.
| 2 | Backup strategy — USB, IPFS, optical? | CIVICVS | | 2 | Backup strategy — USB, IPFS, optical? | CIVICVS |
| 3 | Where is the 3+ TB USB disk attached? | CIVICVS | | 3 | Where is the 3+ TB USB disk attached? | CIVICVS |
| 4 | Kane Fabric participant mail, send and receive | Cross-project | | 4 | Kane Fabric participant mail, send and receive | Cross-project |
| 5 | Tolerance model for `VOLUME_MM3` — see F-034 | CIVICVS, **not yet** | | 5 | Tolerance model for the three discretisation-limited keys — see §7 | CIVICVS, **ready to decide** |
Question 4 is real and unaddressed. Containers do not send mail by standard and Question 4 is real and unaddressed. Containers do not send mail by standard and
nothing can reach `vmbr1` from outside, so receiving has no path at all. It needs nothing can reach `vmbr1` from outside, so receiving has no path at all. It needs
@@ -375,6 +445,9 @@ a design conversation, not a configuration change.
## 12. Commit log ## 12. Commit log
``` ```
114189c docs: PRECISION.md -- scope, guarantees and limits, for lay readers
7b3182c port: the 3x and 4x profile catalogues; build() now exists
af196a2 docs: one canonical handoff, rewritten in place; F-035
009fcce docs: handoff for the 19 AUG session 009fcce docs: handoff for the 19 AUG session
86a47c3 rounding: record F-034, arc segment counts tip on the last bit 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 8d79431 geom: port sb-core and sb-profiles, the N-generic arrangements