step.motion.path.passes.to_absolute

to_absolute pass — turn dead-reckoning relative runs into ONE navigate-to-pose.

A contiguous run of consecutive known-endpoint linear / turn / diagonal segments (no live condition) is converted into a SINGLE GotoWaypoints navigate-to-pose move. GotoWaypoints captures the localization pose ONCE at the run start (its on_start) as a single anchor, precomputes the ABSOLUTE world target for each of the run’s waypoints (fixed geometry, known at compile time), then regulates onto each target in sequence using the particle filter as feedback — so the open-loop dead-reckoning legs become one closed-loop run that shrugs off odometry drift. Anchor scope is PER RUN: each contiguous run captures its own anchor at its first leg, and every waypoint within the run is relative to that ONE anchor (not chained leg-to-leg), so drift does NOT accumulate across legs.

Crucially this requires ZERO executor changes: the replacement node is an inline (blocking) SideAction, and the executor already runs inline side actions via await step.run_step(robot). GotoWaypoints is a MotionStep, whose run_step drives its full on_start / on_update / on_stop loop to completion — so the run drives its closed-loop controller through every target.

Run-detection: a MAXIMAL run is a stretch of consecutive Segment nodes that are ALL kind in ("linear", "turn", "diagonal"), has_known_endpoint is True and whose condition is “baked” — i.e. None, or a bare relative after_cm whose distance is folded into distance_m at lowering time (the runtime odometer stop is kept but the geometric endpoint is exact). Anything else — a SideAction, a None deferred placeholder, an arc / follow_line / spline segment, a segment with a live (early-stopping / sensor / absolute) condition or an unknown endpoint — BREAKS the run and passes through unchanged. Body-frame pose is integrated from (x=0, y=0, heading=0) at the run start, reusing the SAME forward/left integration as segments_to_spline_waypoints (in spline.py). ONE waypoint is collected per LINEAR segment endpoint (preserving the path shape — no corner cutting); turns between linears fold into the following waypoint’s target heading. All the run’s waypoints feed ONE emitted GotoWaypoints.

SENSOR-bounded single-axis linear legs. A linear segment with NO known endpoint and a real (non-baked) condition — e.g. strafe_left().until( on_black) — does NOT join a known-endpoint run, but it pins 2 of 3 world DOF (the cross-axis position + heading) and leaves only the travel axis free until the sensor fires. Such a leg is converted into a single inline SideAction(AbsoluteHoldMove): a closed-loop-on-localization step that holds the 2 pinned DOF on their absolute targets (correcting drift DURING the move, read-only on the particle filter) while driving the free axis open-loop until the original .until() condition fires. This keeps the WHOLE to_absolute path on the particle filter rather than odometry dead reckoning. Each AbsoluteHoldMove re-anchors at its own on_start, so the next known run re-anchors naturally and a sensor leg flushes any in-progress known run.

Best-effort: segments that don’t qualify are left untouched; the pass never crashes. .until(after_cm) legs qualify automatically — their distance is recovered at lowering time, so a BAKED after_cm leg becomes a GotoWaypoints waypoint, NOT an AbsoluteHoldMove. follow_line / spline / arc / diagonal sensor legs are left untouched.

to_absolute is driven by the builder as a MODE flag (optimizer._absolute), not a representation-changing pass. When splinify() is also chained the whole path becomes ONE absolute spline and this per-leg pass is skipped; with only to_absolute() the builder appends this pass for the per-leg conversion. It leaves produces at the default (SAME) so chaining with the terminal splinify() is allowed.

Attributes

Waypoint

One GotoWaypoints leg, fully resolved at COMPILE time:

Classes

ToAbsolutePass

Convert each known-endpoint relative run into ONE closed-loop GotoWaypoints.

Module Contents

step.motion.path.passes.to_absolute.Waypoint

One GotoWaypoints leg, fully resolved at COMPILE time: (rel_forward_m, rel_left_m, abs_dx_m, abs_dy_m, heading_kind, heading_value).

GotoWaypoints composes it against the single run anchor as:

world = anchor.pos + Rot(anchor.heading)·(rel_forward, rel_left) + (abs_dx, abs_dy)
theta = anchor.heading + heading_value   if heading_kind == "rel"
        heading_value                     if heading_kind == "abs"
class step.motion.path.passes.to_absolute.ToAbsolutePass

Convert each known-endpoint relative run into ONE closed-loop GotoWaypoints.

Pure node→node pass. Replaces each maximal run of qualifying linear / turn segments with a SINGLE inline SideAction(GotoWaypoints) node carrying all the run’s body-frame waypoints — one per linear endpoint. The GotoWaypoints captures one anchor per run at start and drives to the resulting ABSOLUTE world targets in sequence (so drift does not chain leg-to-leg).

Additionally converts each SENSOR-bounded single-axis linear leg (no known endpoint, real non-baked condition — e.g. strafe_left().until( on_black)) into a single inline SideAction(AbsoluteHoldMove) that holds the cross-axis position + heading absolutely (2 DOF, read-only on localization) while driving the free axis until the sensor fires. Such a leg flushes any in-progress known run first.

Non-qualifying nodes pass through unchanged. .until(after_cm()) legs qualify automatically — their distance is recovered at lowering time, so a baked after_cm leg stays a GotoWaypoints waypoint (NOT an AbsoluteHoldMove). follow_line / spline / arc / diagonal sensor legs pass through untouched.

to_absolute is driven by the builder as a MODE flag (optimizer._absolute), not a representation-changing pass: when splinify() is also chained the whole path becomes one absolute spline and this per-leg pass is skipped; when it isn’t, the builder appends this pass to do the per-leg conversion. It therefore leaves produces at the default (SAME) so chaining with the terminal splinify() is allowed.

name = 'to_absolute'
run(nodes: list[step.motion.path.ir.PathNode | None]) list[step.motion.path.ir.PathNode | None]