ADR 0002 — The path IR has two levels
Status: accepted (2026-08-21) · falsification test PASSED in Phase 4 — see below
Context
Many reasonable ways exist to describe a tube centreline — polyline with per-vertex fillet radii, a turtle/bender program, a parametric function, arcs and lines, splines. They should all compile down to one shared form. The question is what that form is.
The ecosystem’s native currency is a list of 4×4 transforms. Three of four independent design proposals wanted exactly that.
Decision
Two levels.
utPath(the spine) — an ordered chain of exact segments:L(line),A(arc, parameterised as STEP/IGESaxis2_placement_3d),P(sampled, with per-sample tangents). Sparse, exact, carrying no roll and no resolution.utStations—[position, tangent, roll-normal, arclength], produced from the spine by exactly one function,ut_stations().
Frontends produce a spine and never touch frames. The backend consumes stations and never infers orientation.
Why not a bare transform list
- Parallel transport along a circular arc has a closed form — rotate the roll reference about the arc’s own axis by the arc’s own sweep angle. Exact, one line. Sampling to points forfeits it.
- Terminal and tangency-point tangents stay exact rather than smoothed. Measured error when left to inference: 1.18° at a port, 7.5° at an arc tangency point. Across a 20 mm flange, 1.18° is ~0.4 mm of gap at the rim.
- A 100 mm straight costs 2 stations instead of ~200, and each bend takes facets
from
$fa/$fsapplied to its own radius. - The minimum-bend-radius check becomes a one-line assert. This cannot be
delegated to the geometry: an
od=12tube swept through anr=4bend self-intersects and CGAL reportsSimple: yeswith zero warnings.
The transform list still exists — as the output of ut_stations(), produced at
the backend boundary.
Consequences
Roughly 80 extra lines, and the layer most at risk of being ceremony.
Falsification test, stated in advance: Phase 4 ships the turtle frontend. If the turtle does not fall out of the spine cleanly — if the spine has to grow a case, or the turtle reimplements any frame or tessellation logic — delete the spine layer in favour of a bare station list. Do not defend it.
Falsification result (Phase 4)
The test was stated in advance, in this ADR and in the phase plan: if the turtle frontend did not fall out of the spine cleanly — if the spine had to grow a case, or the turtle reimplemented any frame or tessellation logic — the spine layer would be deleted in favour of a bare station list, not defended.
It fell out cleanly. Recorded as facts, not recollection:
- No core module changed.
ut_path.scad,ut_profile.scad,ut_mesh.scad,ut_core.scadandut_math.scadwere checksummed beforeut_turtle.scadwas written and verified byte-identical after both new frontends were finished. - Neither frontend references frame or tessellation logic at all — zero
mentions of
ut_fragments,ut_ref_fallback,ut_ortho,_ut_transport,ut_st_matorut_stationsbetween them. ut_turtle.scadis 65 lines;ut_curve.scadis 53.
The strongest single piece of evidence is the turtle’s roll command. It
emits no geometry: it rotates the turtle’s own up-vector, which changes the plane
of every subsequent bend and therefore the shape of the path, and then it is
gone. Nothing about roll enters the spine, because a bender rotating the
workpiece does not twist the tube. SPINE-5 — the IR carries no roll — survived
contact with the one frontend that has an explicit roll command, which is the
case most likely to have broken it.
Two frontend-side responsibilities were confirmed as belonging to the frontend
rather than the spine, and neither required a change to the IR: splitting a bend
beyond 180° into two arcs (SPINE-4), and deduping coincident samples before
building a P segment (STATION-1).
The spine stays.
Known limit, recorded 2026-08-21
SPINE-5 says the IR carries no roll and no resolution. The roll half holds
everywhere — ut_turtle’s roll command proved it above. The resolution half
does not hold for P segments: ut_curve bakes its sample count into the
spine, and $fa/$fs are inert on the result (measured: 161 stations at both
$fa=24 and $fa=0.5, where an arc path gives 7 and 183).
docs/reference/ir.md now states this as a limit rather than claiming the invariant. The
fix is a biarc fitter, which exists as a working prototype in
spikes/biarc_fitter.scad and is deliberately not built — see issue #17
for the measurements that demoted it.