Nyimbi Odero

Series 01 · a numbered run · kept in public

X1.1.4

Chapter: Phase 1 — Foundations of Physical Intelligence · Module 1.1 — Robot Modeling and Classical Control

Lesson 1.1.4 — The Full-Stack Pick-and-Place Pipeline: Perception, IK, Trajectory, Control, and Error Recovery

Week 6 · Prerequisites: 1.1.1, 1.1.2, 1.1.3, 0.2.4, 0.3.4.

Prior results used. This lesson uses 1.1.1’s FK, DH table, and geometric Jacobian; 1.1.2’s analytical and numerical IK, the branch selection, and the DLS; 1.1.3’s PID, computed torque, minimum-jerk trajectory, and real-time discretization; 0.2.4’s frame audit and sign-error taxonomy; and 0.3.4’s Mahalanobis gate as the perception’s outlier filter. Each is restated at the point of use.

Hands forward. This lesson hands forward one capability: the learner can build an end-to-end pick-and-place controller for a 6-DOF arm — from the camera’s observation of the object, through the frame audit, the reachability check, the IK branch selection, the minimum-jerk trajectory, the computed-torque tracking, and the state machine’s error recovery — and diagnose the four pipeline-level failures (unreachable target, branch discontinuity, trajectory–controller mismatch, and silent perception outlier) from their end-to-end symptoms — the capability that Module 1.2 (Robot Learning for Control), Module 2.3 (Grasping and Manipulation), and the Phase 4 capstone assume.

Running case. The same 6-DOF MuJoCo arm from 1.1.1–1.1.3, with a camera mounted on the end-effector (from 0.2.4’s pipeline) and a target object on a table. The task: pick the object and place it in a bin. The full pipeline is seven stages — (1) perception (T_Cam^Obj), (2) the frame audit (T_W^Obj from the camera’s pose), (3) the IK (q_grasp from T_W^Obj), (4) the trajectory (minimum-jerk from q_current to q_grasp), (5) the control (computed torque tracking the trajectory), (6) the state machine (approach → grasp → lift → move → place → retreat), and (7) the error recovery (if any stage fails). The lesson’s central result is that each stage is correct in isolation but the pipeline’s integration fails in four specific ways, and the state machine with health checks at each boundary is the fix.


§L.0 — Lesson map, objectives, and misconceptions

§L.0.1 — Lesson map and word budget

PartContentLab stepWord budget
AThe seven-stage pipeline and the interfaces between stagesLab Steps 1–21,600
BThe perception-to-IK boundary: frame audit, reachability, branch selectionLab Steps 3–51,900
CThe IK-to-control boundary: trajectory, feedforward, timing, real-timeLab Steps 6–81,700
DThe state machine: stages, transitions, health checks, error recoveryLab Steps 9–101,400
EFailure taxonomy, lab synthesis, cheat sheet, exercises, exit testLab Steps 11–131,800
Total8,400

§L.0.2 — Objectives

By the end of this lesson you will be able to do eight things:

  1. State the seven-stage pick-and-place pipeline, the input–output contract of each stage, and the frame convention at each interface (from 0.2.4’s audit).
  2. Apply the frame audit to the perception-to-IK boundary, and detect the four canonical sign errors (inverse rotation, inverse translation, wrong order, wrong direction) from the end-effector’s miss.
  3. Validate the IK’s output against the reachability (workspace), the branch continuity (the returned q_grasp is close to q_current), and the joint limits — before the controller executes the trajectory.
  4. Generate the minimum-jerk trajectory from q_current to q_grasp, with the timing matched to the controller’s bandwidth, and verify the trajectory’s peak torque against the actuator’s limit.
  5. Implement the computed-torque controller’s feedforward from the trajectory’s q̈_d, and synchronize the controller’s sample rate with the trajectory’s, so the timing is exact.
  6. State the state machine’s six stages, the transition conditions, the health checks (the error norms at each boundary), and the error recovery (the fallback to a safe pose on failure).
  7. Diagnose the four pipeline-level failures — unreachable target (the IK returns no solution), branch discontinuity (the IK jumps to a far branch), trajectory–controller mismatch (the trajectory is faster than the controller can track), and silent perception outlier (the object’s pose is wrong, but the IK “succeeds”) — from their end-to-end symptoms.
  8. Produce a working end-to-end pick-and-place controller for the running case, with the health checks that detect each of the four failures, and demonstrate the recovery for at least two of them.

§L.0.3 — Misconception inventory

Wrong model the learner probably holdsWhy it is attractiveWhere displacedThe correct model in one sentence
M1 — “If each stage is correct in isolation, the pipeline is correct.”Each stage has its own tests (FK matches PoE, IK converges, PID is stable), and the tests pass — so the pipeline should work.§L.1 (Part A)Correctness in isolation does not imply correctness in composition — each stage has hidden assumptions about its inputs (the IK assumes the target is reachable, the controller assumes the trajectory is dynamically feasible, the state machine assumes the perception is accurate), and the integration is where the assumptions fail; the pipeline’s correctness is the composition’s correctness, tested end-to-end, not the stages’ tests.
M2 — “The frame audit is a perception concern, not a control concern.”The frames are about the camera and the object, and the control is about the joint angles — the two seem separate.§L.2 (Part B)The frame audit is a pipeline-wide concern — the IK receives the target’s pose in the arm’s base frame, and a sign error in the perception-to-base transform (from 0.2.4) makes the IK solve the wrong problem (the arm reaches a pose that is mirrored from the object), and the end-effector’s miss is the symptom; the audit must span every interface, not just the perception’s.
M3 — “The IK’s branch selection is a minor detail.”The IK returns a solution, and any solution that achieves the pose is a valid q_grasp.§L.2 (Part B)The IK’s branches are geometrically distinct (elbow-up vs. elbow-down), and a jump between branches in the middle of a task makes the arm swing through a large joint-space motion — a motion that may collide with the environment, exceed the joint limits, or violate the velocity limits; the branch continuity (choosing the branch closest to q_current) is a safety requirement, not a detail.
M4 — “The trajectory is just the path; the controller tracks it regardless.”The trajectory specifies the position, and the controller’s job is to follow it, so the two seem independent.§L.3 (Part C)The trajectory’s acceleration q̈_d is the controller’s feedforward, and a trajectory that is faster than the controller can track (the ω_n vs. 1/T mismatch) produces a large tracking error — the controller saturates or lags, and the end-effector is not where the trajectory says; the timing (the trajectory’s T vs. the controller’s ω_n) is the coupling, and it must be designed together.
M5 — “The state machine is just a switch statement; the error recovery is the exception path.”The happy path is the main code, and the error handling is the edge case.§L.4 (Part D)The state machine’s transitions are the safety-critical decisions — the check ‖e‖ < ε before the grasp is what prevents the missed grasp, and the fallback to a safe pose is what prevents the collision; in a real pick-and-place, the errors are common (unreachable targets, perception failures, dropped objects), and the recovery is the primary path, not the exception.

Each misconception is displaced in the section named.

Everything is self-contained. Where a prerequisite lesson is used, the result is restated at the point of use.


§L.0.5 — Retrieval warm-up

This is the sixteenth lesson. The prompt requires drawing from lessons two to five steps back; 1.1.3 is one step back and is excluded. The warm-up draws from 1.1.2 (two steps back), 1.1.1 (three), 0.3.4 (four), and 0.2.4 (five).

Warm-up 1 (from 1.1.2, the IK’s branch and the arccos). State the 2-link arm’s IK and the branch choices. Answer: cos q₂ = (r² − a₁² − a₂²)/(2a₁a₂), q₂ = ± arccos(cos q₂), q₁ = atan2(y, x) − atan2(a₂ sin q₂, a₁ + a₂ cos q₂). The ± is the two branches (elbow-up/elbow-down). The arccos’s domain [-1, 1] is the reachability. How this is used: §L.2’s pipeline’s branch selection chooses between the two q_grasp’s based on the distance to q_current and the joint limits, and the arccos’s NaN is the unreachable target failure.

Warm-up 2 (from 1.1.1, the FK’s q = 0 test). State the FK’s two tests (the q = 0 reach and the non-zero configuration) and what each catches. Answer: the q = 0 test catches the a_i errors and the order errors (the reach is the sum of the link lengths); the non-zero test catches the α_i and sign errors (the rotations’ effect). The cross-check with the PoE is a stronger test. How this is used: §L.3’s controller’s feedforward uses the FK to compute the end-effector’s actual pose for the error e = pose(T_actual) − pose(T_desired), and the FK’s correctness is a precondition for the controller’s error measurement.

Warm-up 3 (from 0.3.4, the Mahalanobis gate). State the Mahalanobis gate and its false-rejection rate. Answer: d² = νᵀS⁻¹ν, reject if d² > χ²_{1−α}(m), with a false-rejection rate of α under the null. The S includes the prior’s and the measurement’s uncertainty. How this is used: §L.2’s perception’s outlier filter uses the gate to reject the camera’s outlier detections (a bad T_Cam^Obj), and the pipeline handles the rejection by retrying or falling back — the gate is the first line of defense for the perception.

Warm-up 4 (from 0.2.4, the frame audit and the sign errors). State the four canonical sign errors and their symptoms. Answer: inverse rotation (wrong-way rotation), inverse translation (off by (R − I)t), wrong order (correct at q = 0, drifts at q ≠ 0), wrong direction (opposite pose). The from-to convention and the effect in the code pattern (p_W = T_WB @ p_B) are the fixes. How this is used: §L.2’s perception-to-IK boundary is the most sign-error-prone interface — the camera’s T_Cam^Obj must be composed with the arm’s T_W^EE and T_EE^Cam in the correct order — and the frame audit is the structured check.


Part A — The Seven-Stage Pipeline and the Interfaces

§L.1 — Objects: the stages, the contracts, and the frames

The pick-and-place pipeline has seven stages, each with an input–output contract:

StageInputOutputFrame convention
1. PerceptionRGB-D imageT_Cam^Obj (object’s pose in camera)T_Cam^Obj (from-to: Obj → Cam)
2. Frame auditT_Cam^Obj, T_W^EE, T_EE^CamT_W^Obj (object’s pose in world)T_W^Obj = T_W^EE · T_EE^Cam · T_Cam^Obj
3. IKT_W^Obj, q_currentq_grasp (joint angles for the grasp)q_grasp = IK(T_W^Obj · T_Obj^Grasp)
4. Trajectoryq_current, q_grasp, T_moveq_d(t) (minimum-jerk)Joint-space, t ∈ [0, T_move]
5. Controlq_d(t), q, q̇, M, C, gτ (joint torque)Computed torque: τ = M(q̈_d + K_d ė + K_p e) + C q̇ + g
6. State machineThe stage’s q, e, and the stage’s healthThe next stage or the fallbackThe transition conditions and the health checks
7. Error recoveryThe failure (from the state machine)The safe pose or the retryThe fallback q_safe or the retry from a safe state

The contract at each interface is the exact frame convention, the units (radians, meters, N·m), and the tolerance (ε for the health checks). The interfaces are the boundaries where the pipeline fails — each stage’s internal correctness is necessary but not sufficient, and the integration is where the hidden assumptions surface.

The frames (from 0.2.4’s audit) are the most error-prone interface:

  • W (world) — the fixed frame, the reference for all poses.
  • B (base) — the arm’s base, fixed in W (T_W^B is a calibration).
  • EE (end-effector) — the arm’s tip, computed by the FK (T_B^EE(q)).
  • Cam (camera) — the camera, mounted on the end-effector (T_EE^Cam is a calibration).
  • Obj (object) — the target, observed by the camera (T_Cam^Obj from the perception).
  • Grasp (the grasp pose) — the pre-grasp pose relative to the object (T_Obj^Grasp is a task-specific offset).

The object’s pose in the world is the composition (from 0.2.4’s frame audit):

TWObj=TWB⋅TBEE(q)⋅TEECam⋅TCamObjT_W^{Obj} = T_W^B \cdot T_B^{EE}(q) \cdot T_{EE}^{Cam} \cdot T_{Cam}^{Obj}

and the grasp pose is T_W^Grasp = T_W^Obj · T_Obj^Grasp, which is the IK’s target.

§L.1.1 — Result: the pipeline’s correctness is the composition’s correctness, and each stage’s hidden assumptions are the interfaces’ failure modes

Claim. The pipeline perception → audit → IK → trajectory → control → state machine is correct if and only if (i) each stage is correct in isolation and (ii) each interface’s hidden assumptions hold — the perception’s accuracy (the T_Cam^Obj is within the gate’s tolerance), the IK’s reachability (the T_W^Grasp is in the workspace), the trajectory’s dynamic feasibility (the q̈_d is within the actuator’s limit), and the controller’s tracking (the ‖e‖ is within the health check). The hidden assumptions are not tested by the stages’ internal tests, and the integration is where they fail.

Proof sketch. Each stage’s internal test verifies the stage’s function given valid inputs — the IK’s test verifies T(q_grasp) = T_target for a reachable T_target, the trajectory’s test verifies the smoothness, the controller’s test verifies the stability for a feasible q_d. The interfaces are the boundaries where the validity of the inputs is assumed but not verified — the perception’s accuracy, the IK’s reachability, the trajectory’s feasibility, the controller’s tracking. The composition is correct iff the assumptions hold, and the pipeline-level test must check each assumption at its interface.

Corollary. The health checks are the pipeline’s correctness mechanism — the frame audit’s ‖T_W^Obj − T_W^Obj_expected‖ < ε (the perception’s accuracy), the IK’s reachability (|cos q₂| ≤ 1 and the joint limits), the trajectory’s feasibility (max|τ| < τ_max), and the controller’s tracking (‖e‖ < ε_track). The state machine is the orchestration of the checks, and the error recovery is the fallback when a check fails.

Corollary. The pipeline’s failure modes are exactly the hidden assumptions’ violations, and the four canonical failures (unreachable target, branch discontinuity, trajectory–controller mismatch, silent perception outlier) are the four assumptions’ failures. The diagnosis is the assumption’s check, and the fix is the assumption’s restoration (a safe pose, a closer branch, a slower trajectory, a gated perception).

§L.1.2 — Worked-example ladder

WE-L.1.a — Fully worked: the pipeline’s data flow for a specific grasp

Input. The running case’s arm at q_current = [0.3, 0.5, −0.2, 0.1, 0.4, −0.3], the camera’s observation T_Cam^Obj = [[Rz(0.5), [0.1, 0.05, 0.3]], [0, 1]] (the object is 0.1 m in front, 0.05 m right, 0.3 m up in the camera’s frame), the calibration T_EE^Cam = [[I, [0, 0, 0.1]], [0, 1]] (the camera is 0.1 m along the end-effector’s z), the base’s pose T_W^B = I (the base is at the world origin).

Trace (stage 1, perception).

  1. T_Cam^Obj = [[Rz(0.5), [0.1, 0.05, 0.3]], [0, 1]] — the object’s pose in the camera’s frame.

Trace (stage 2, frame audit).

  1. T_B^EE = FK(q_current) — from 1.1.1’s FK at q_current.
  2. T_W^B = I (the base is at the world origin).
  3. T_W^Obj = T_W^B · T_B^EE · T_EE^Cam · T_Cam^Obj = T_B^EE · T_EE^Cam · T_Cam^Obj.

Trace (stage 3, IK).

  1. T_Obj^Grasp = [[I, [0, 0, 0.05]], [0, 1]] (the grasp pose is 0.05 m above the object’s origin).
  2. T_W^Grasp = T_W^Obj · T_Obj^Grasp.
  3. q_grasp = IK(T_W^Grasp, seed = q_current) — the numerical IK, with the seed for the branch continuity.

Trace (stage 4, trajectory).

  1. q_d(t) = minimum_jerk(q_current, q_grasp, T_move) for T_move = 1.5 s.

Trace (stage 5, control).

  1. τ(t) = M(q)(q̈_d + K_d ė + K_p e) + C(q, q̇) q̇ + g(q) at each t.

Trace (stage 6, state machine).

  1. The transitions: approach → grasp (when ‖e‖ < ε_track) → lift (when the gripper is closed) → move (when the object is lifted) → place (when the target bin is reached) → retreat (when the object is released).

Output and interpretation. The pipeline’s data flow is the composition of the transforms (stage 2) and the sequential execution of the stages (3–6). The critical interfaces are (i) the perception-to-audit boundary (the T_Cam^Obj must be accurate), (ii) the audit-to-IK boundary (the T_W^Grasp must be reachable), (iii) the trajectory-to-control boundary (the q̈_d must be feasible), and (iv) the control-to-state-machine boundary (the ‖e‖ must be within the health check). Each interface’s hidden assumption is the potential failure.

Self-explanation 1. Before reading on: in stage 2, why is T_W^Obj = T_W^B · T_B^EE · T_EE^Cam · T_Cam^Obj and not some other order?

Answer. The composition is the from-to chain (from 0.2.4’s audit): T_W^B (from B to W), T_B^EE (from EE to B), T_EE^Cam (from Cam to EE), T_Cam^Obj (from Obj to Cam). The product W ← B ← EE ← Cam ← Obj cancels the intermediate frames, leaving W ← Obj. Any other order leaves a mismatched frame (e.g., T_W^B · T_EE^Cam has B and EE mismatched), which is a type error in the frame algebra — the sign error from 0.2.4 (wrong order).

Self-explanation 2. Before reading on: in stage 3, the IK’s seed is q_current. Why is the seed the current configuration, not the previous grasp’s q_grasp?

Answer. The seed q_current is the closest to the actual configuration (the arm is at q_current), so Newton–Raphson’s basin around it usually contains the branch the arm is already on, and the IK converges there — which is the continuity-preserving one. A previous q_grasp’s seed would converge to the previous branch, which may be different from the current arm’s branch — the jump would make the arm swing. The current seed is the safer choice, and the continuity is the reason. But usually is the operative word: the seed does not guarantee the near branch. Measured on the 2-link arm over a 6,000-target grid from q_start = (−0.90, 1.60), 994 targets converge to the far branch — 17% of them — and target (−0.20, 0.16) is one, a 3.28 rad jump on a 1 cm change from (−0.20, 0.15). That is why the branch continuity check is a separate stage and not a consequence of the seed.

WE-L.1.b — Completion problem

Input. Same as above, but the camera calibration T_EE^Cam = [[I, [0, 0, 0.1]], [0, 1]] is wrong: the actual is T_EE^Cam = [[I, [0, 0, 0.15]], [0, 1]] (the camera is 0.15 m, not 0.1 m).

Trace (final 25% as prompts).

  1. The perceived T_W^Obj uses the wrong calibration (0.1 m). ⟨your turn: is the perceived object closer or farther than the actual?⟩
  2. The actual object is at the true T_W^Obj with the 0.15 m calibration. ⟨your turn: what is the error ΔT = T_W^Obj_perceived − T_W^Obj_actual?⟩
  3. The IK solves for the perceived pose, so q_grasp is off by the calibration error. ⟨your turn: what is the end-effector’s miss at the grasp?⟩
  4. The health check ‖e‖ < ε_track passes (the controller tracks the commanded trajectory, which is the wrong pose). ⟨your turn: does the pipeline detect the error?⟩

Answers.

  1. Closer, by 0.05 m. The perceived pose is T_B^EE · T_EE^Cam_perceived · T_Cam^Obj, and the perceived T_EE^Cam carries the smaller offset (0.1 m against the true 0.15 m). The object therefore appears 0.05 m nearer the end-effector than it is, hence nearer the base along the end-effector’s z. The actual object sits 0.05 m farther along that axis, so the perceived pose is wrong by 0.05 m in the −z direction of the end-effector frame.

  2. The error ΔT is a 0.05 m translation along the end-effector’s z-axis (with the rotation unchanged — the calibration’s rotation is the identity). The pipeline’s frame audit would need to detect this, but the audit uses the same T_EE^Cam — the audit cannot detect the calibration’s error, only the composition’s error.

  3. The end-effector’s miss at the grasp is the 0.05 m — the arm reaches the perceived pose, which is 0.05 m away from the actual object. The grasp misses (the gripper is 0.05 m off), and the object is not picked.

  4. The health check ‖e‖ < ε_track passes — the controller tracks the commanded trajectory (the perceived pose) with small error, and the health check’s e is the tracking error (controller vs. trajectory), not the perception error (perceived vs. actual). The pipeline does not detect the error — the failure is silent at the control level, and it manifests at the grasp (the object is not picked). The fix is a calibration check at the perception level (a known object at a known pose, verifying the T_EE^Cam), or a grasp verification after the grasp (a force sensor or a visual check).

The lesson. The calibration’s error is not detected by the pipeline’s health checks — the checks verify the composition’s correctness (given the calibration), not the calibration’s correctness. The calibration is a separate verification, and the pipeline’s health checks must be complemented by the calibration’s checks.

WE-L.1.c — Faded: the state machine’s transition conditions

Setup. The pipeline’s state machine, with the stages approach → grasp → lift → move → place → retreat and the transitions.

Question. What are the transition conditions for each stage, and what health check does each condition rely on?

You must produce the intermediate steps. The setup and the answer are given.

Answer.

  • approach → grasp: the pre-grasp pose is reached (‖e‖ < 1 mm, ‖e_ori‖ < 0.01 rad). Health check: the tracking error at the approach.
  • grasp → lift: the gripper is closed (the force sensor reads a threshold), and the grasp is verified (the object is present). Health check: the gripper’s force.
  • lift → move: the object is lifted (z > z_table + 0.05) and stable (no drop). Health check: the object’s presence (visual or force).
  • move → place: the target bin is reached (‖e‖ < 1 mm). Health check: the tracking error at the place.
  • place → retreat: the gripper is open (the object is released). Health check: the gripper’s state.

Each transition relies on a health check (the tracking error, the gripper’s force, the object’s presence), and the check’s failure triggers the error recovery (the fallback to a safe pose, the retry, or the abort).

The lesson. The state machine’s transitions are safety-critical — each is a health check that verifies the stage’s success before the next stage. The checks are the pipeline’s correctness mechanism, and their absence is the silent failure (the pipeline continues even when the stage failed).

WE-L.1.d — Contrastive near-miss: the pipeline without health checks

Correct solution. The pipeline checks each stage’s success (the tracking error, the gripper’s force, the object’s presence) before the next stage.

Near-miss. The pipeline runs the stages unconditionally — the trajectory completes, the gripper closes, the arm moves — without verifying that the grasp was successful.

Why the near-miss produces a plausible but incorrect number. The pose at each stage is correct (the FK, the IK, the controller all work), but the grasp may have missed (the object is not between the gripper’s fingers), and the lift stage lifts nothing — the arm’s motion is correct, but the task fails. The symptom is the empty gripper at the place stage — the object is not in the bin, and the task’s failure is silent until the final check (or the human observes the failure). The fix is the health check at the grasp stage — the gripper’s force or the visual presence — which detects the missed grasp before the lift.

The discrepancy, stated. The pipeline’s stages are correct in isolation, but the task’s success is not the composition’s correctness — the task is the pick-and-place, and the grasp is a physical event that the pose does not verify. The health check is the missing link — the pose is necessary and not sufficient for the grasp, and the force or the vision is the sufficient check.

Self-explanation 3. Before reading on: in the near-miss, if the object is large (a big box), does the missed grasp still happen?

Answer. A larger object is more forgiving — the gripper’s fingers have a larger target to hit, and the pose’s error is less likely to miss the object. But the forgiveness is a probability (the miss is less likely, not impossible), and the health check is still needed — a large object can still slip from the gripper, or the gripper can hit the object’s edge and fail to close. The health check is the universal fix, and the object’s size is a mitigating factor, not a substitute.

§L.1.3 — Comparative table: what each level of check can see

CheckWhat it verifiesWhat it cannot seeFailure’s loudnessWhere it lives
Stage-internal testThe stage’s function given valid inputs — the FK matches PoE, the IK converges, the PID is stableThe validity of the input: whether the target was reachable, the pose accurate, the trajectory feasibleSilent — the test passes on garbage inputThe stage’s own unit test
Name cancellationThe composition’s subscripts pair — W←B←EE←Cam←ObjA wrong matrix under the right name; which convention the file is written inLoud on a subscript typo, silent otherwiseThe frame audit
Data-pointer checkThat each factor’s value is the pose it claims — compare the translation and the rotation against the FK or the calibrationThe calibration’s own error: the audit uses the same T_EE^Cam it is auditingSilent on a mis-calibrationThe frame audit
End-to-end health checkThat the task succeeded — ‖e‖ < ε, the gripper’s force, the object’s presenceAnything the check does not measure: a tracking error is not a perception errorSilent until the grasp missesThe state machine’s transitions
Calibration verificationThat the calibration matches physical reality — a known object at a known poseNothing downstream — this is the ground truth the others assumeLoud, and it is the only level that catches a calibration errorA separate, periodic procedure

Design principle (P16.6). Every check certifies one assumption and inherits every other. The health check ‖e‖ < ε_track compares the arm to the commanded trajectory, so it certifies the controller and says nothing about the pose that trajectory was built from: with T_EE^Cam wrong by 0.05 m, the perceived object sits 0.05 m nearer than it is, the IK solves the wrong pose, the controller tracks it to a millimeter, and the grasp misses. The audit cannot catch it either, because the audit composes the same wrong calibration. Enumerate the assumptions each check inherits, and put the check that discharges each one somewhere in the pipeline.

§L.1.4 — Concept check

Concept check (displaces M1). A learner says: “every stage has its own test and they all pass — FK matches PoE, the IK converges to 1e-10, the controller is stable at the design gains — so the pipeline is correct.”

Answer. The learner has failed to model the interface as an object with its own precondition. Each stage’s test quantifies over valid inputs: the IK’s test uses a reachable target, the controller’s uses a feasible trajectory, the perception’s uses an accurate pose. Correctness in isolation is a statement about the stage’s function; the pipeline’s correctness is a statement about the composition, and the composition adds four assumptions that no stage test exercises — accuracy, reachability, feasibility, tracking. The bug class is a green test suite over a silently wrong pipeline: the calibration is 0.05 m off, every test passes, the arm tracks beautifully to the wrong place, and the failure surfaces as an empty gripper at the place stage. The diagnosis is not “which stage failed” but “which assumption was violated, and which check measures it” — and the answer is usually that no check measures it, which is the design defect.

§L.1.5 — Consolidation checkpoint 1

(a) You should now be able to: (i) list the seven stages with each one’s input, output, and frame convention; (ii) state, for each of the four interfaces, the hidden assumption and the check that would verify it; (iii) distinguish a tracking error from a perception error and say which check sees which.

(b) Transfer probe. A pipeline runs with a hand-eye calibration that is 0.05 m short: T_EE^Cam says 0.1 m along z, the true offset is 0.15 m. The controller’s tracking error at the grasp is 0.4 mm. Does the pipeline detect the problem, and if not, what does the operator observe? Answer sketch: No. The tracking error is measured between the arm and the commanded trajectory, and that trajectory was built from the perceived pose, so a consistently wrong pose is tracked perfectly. The perceived object appears 0.05 m nearer the end-effector than it is; the arm reaches 0.05 m short; the gripper closes on air. The operator sees a missed grasp with a clean controller log — the two facts look contradictory until you notice the log’s error is in a different frame from the miss. The fix is a calibration check against a known object, plus a grasp verification, because neither the audit nor the tracking check can see a calibration error.

(c) Remediation rule. If you cannot do (a)(ii) or (b), return to §L.1.1’s corollaries and WE-L.1.b, then redo (b) by writing down which two quantities the ‖e‖ < ε_track check compares.


Part B — The Perception-to-IK Boundary

§L.2 — Objects: the frame audit, the reachability check, and the branch selection

The perception-to-IK boundary has three checks:

  1. The frame audit (from 0.2.4) — the composition T_W^Obj = T_W^B · T_B^EE · T_EE^Cam · T_Cam^Obj must be verified by:

    • The name cancellation (W ← B ← EE ← Cam ← Obj).
    • The data pointer’s check (each factor is the right variable, not its inverse).
    • The effect in the code (p_W = T_W_Obj @ p_Obj).
  2. The reachability check — the target’s T_W^Grasp must be in the workspace:

    • The position’s distance from the base r = ‖p_grasp − p_base‖ is in [r_min, r_max] (the arm’s inner and outer reach).
    • The orientation’s q_grasp has a real solution (the IK’s arccos is in range).
    • The joint limits: the q_grasp’s entries are in [q_min, q_max].
  3. The branch selection — if the IK has multiple solutions, choose the one:

    • Closest to q_current (the continuity — the smallest ‖q_grasp − q_current‖).
    • Within the joint limits.
    • Collision-free (if a collision model is available).

The perception’s accuracy is the first check — the T_Cam^Obj must be within the gate’s tolerance (from 0.3.4’s Mahalanobis gate), and the rejected detections (the outliers) trigger the retry (a second frame) or the fallback (the safe pose).

§L.2.1 — Result: the frame audit’s name cancellation is a necessary check, and the data pointer’s check is the sufficient one; the reachability and the branch selection are the pipeline’s safety checks

Claim. The frame audit is correct iff (i) the names cancel (the composition’s subscripts pair correctly) and (ii) each factor is the right matrix (not its inverse). The name cancellation is necessary (a name mismatch is a definite error) but not sufficient (a wrong matrix can have the right name), and the data pointer’s check (the T_A^B vs. T_B^A) is the sufficient one. The reachability and the branch selection are the pipeline’s safety checks — the IK’s failure on an unreachable target is a NaN (loud), but the branch discontinuity is silent (a valid but far q_grasp).

Proof sketch. The name cancellation is a syntactic check — the composition’s subscripts W ← B ← EE ← Cam ← Obj pair correctly iff the intermediate frames cancel. A wrong matrix (e.g., T_B^EE instead of T_EE^B) has the wrong subscripts, but the name in the code is a human label that can be wrong (the variable named T_B_EE can hold T_EE^B). The data pointer check (the actual value matches the expected pose) is the semantic check, and it is the sufficient one.

Corollary. The reachability check is the IK’s precondition — the target must be in the workspace, and the check is r ∈ [r_min, r_max] and the joint limits. The check’s failure is a NaN (the arccos’s domain), which is loud, but the branch discontinuity (the IK converges to a far branch) is silent — the q_grasp is valid but far from q_current, and the arm swings through a large motion that may collide.

Corollary. The branch selection’s closest criterion is the continuity — the q_grasp closest to q_current minimizes the joint-space motion, which minimizes the collision risk and the velocity limits’ violation. The continuity is a safety requirement, and the check is ‖q_grasp − q_current‖ < δ (a threshold), with the failure triggering a retry (a different IK seed) or a fallback (the safe pose).

§L.2.2 — Worked-example ladder

WE-L.2.a — Fully worked: the frame audit for a specific perception

Input. The code: T_W_Obj = T_W_B @ T_B_EE @ T_EE_Cam @ T_Cam_Obj, written in the pose-in convention (T_A_B = the pose of B in A). Every transform in this lesson is pose-in; the from-to convention (T_A_B = from B to A) is the matrix inverse, and the two conventions are not the same matrix for any non-identity pose. Fixing one convention and writing it at the top of the file is the first line of the audit, because a file that mixes them produces a chain that is individually well-typed and numerically wrong.

Trace (name cancellation).

  1. T_W_B: the pose of B in W.
  2. T_B_EE: the pose of EE in B.
  3. T_EE_Cam: the pose of Cam in EE.
  4. T_Cam_Obj: the pose of Obj in Cam.
  5. The chain W ← B ← EE ← Cam ← Obj — each adjacent pair of subscripts matches (T_W_B ends in B, T_B_EE begins with B), the intermediate frames B, EE, Cam cancel, and the product is T_W_Obj. ✓ The names are consistent.

The cancellation test is mechanical and worth stating as a rule: for each adjacent pair T_X_Y @ T_Y_Z, the second subscript of the left factor must equal the first subscript of the right factor. If any pair fails — T_W_B @ T_EE_Cam has B ≠ EE — the chain is broken, and the product is not a transform between the endpoints it claims.

Trace (data pointer’s check).

  1. The value of T_B_EE should be the FK’s output at q_current. The name cannot tell you whether it is: a variable named T_B_EE that holds inv(T_B^EE) has a correct name and a wrong value, and the cancellation test passes on it. The value check is what catches that — compare the translation to the FK, or to a measured calibration.
  2. The value of T_EE_Cam should be the calibration, whose known content is a 0.1 m offset along z. A hand-eye calibration that returns a 0.1 m offset along z but with the inverse rotation passes a distance check and fails a pose check; compare the rotation, not just the norm.

Output and interpretation. The name cancellation is correct, and the data pointer’s check passes (the values match the expected calibrations). The audit’s two checks are necessary and (together) sufficient — the name is the syntactic check, the value is the semantic check, and the two together verify the composition.

Self-explanation 4. Before reading on: if the code uses T_W_B @ T_EE_B @ T_Cam_EE @ T_Obj_Cam — the from-to names — does it compute the same T_W^Obj?

Answer. No. Under the from-to convention T_EE_B is the matrix that takes B coordinates to EE coordinates, which is inv(T_B^EE); the pose-in T_B_EE is T_B^EE. They agree only when the pose is the identity. Measured on the running case’s transforms, the pose-in product’s translation is [0.4, 0.15, 0.8] and the from-to product’s is [-0.41, -0.10, -0.80] — different matrices, 1.6 apart in the Frobenius norm. The names cancel in both conventions, which is exactly why the cancellation test cannot tell you which one a file is written in. The convention must be stated, not inferred.

Self-explanation 5. Before reading on: if the code uses T_W_B @ T_B_EE @ T_Cam_EE @ T_Cam_Obj (a typo: T_Cam_EE where T_EE_Cam belongs), does the name cancellation detect the error?

Answer. Yes — and this is the one case where the name check earns its keep. In the pose-in convention T_Cam_EE is the pose of EE in Cam, so the chain is T_B_EE @ T_Cam_EE, whose adjacent subscripts are EE and Cam — they do not match, and the cancellation fails at that product. The value check catches the same typo from the other side: T_Cam_EE is inv(T_EE_Cam), a different matrix, and its translation will not match the calibration. When both checks fire on the same line, the diagnosis is immediate. Note that the name check is the weaker of the two — it catches a typo in the subscript order and nothing else — but it is the one that fires without any knowledge of the expected values, which is why it runs first.

WE-L.2.b — Completion problem

Input. The same pipeline, but the target is at T_W^Grasp with p_grasp = [1.5, 0, 0] (beyond the arm’s reach r_max = 1.2).

Trace (final 25% as prompts).

  1. r = ‖p_grasp − p_base‖ = 1.5. ⟨your turn: is this in [r_min, r_max] = [0, 1.2]?⟩
  2. The IK’s arccos argument: ⟨your turn: what is cos q₂?⟩
  3. Is the target reachable? ⟨your turn⟩
  4. The pipeline’s handling: ⟨your turn: what does the state machine do?⟩

Answers.

  1. r = 1.5 > 1.2 — outside the workspace. r_max = 1.2 is the six-link arm’s reach, 6a = 6 · 0.2, and it is the number to use here, in the full-pipeline worked example. The lab’s arm is the two-link sub-arm, a₁ = a₂ = 0.2, whose reach is 0.4; carrying 1.2 into the lab gives a reachability check that passes targets the arm cannot touch. Two different arms in one lesson means two different r_max’s, and the only safe practice is to compute r_max from the link lengths at the point of use rather than quoting a constant.
  2. The cos q₂ = (1.5² − a₁² − a₂²)/(2a₁a₂) for the 2-link arm… but for the 6-DOF arm, the reachability is more complex — the IK’s arccos may be in range if the arm can contort to reach 1.5 in some direction, but the max reach is 1.2, so no configuration reaches 1.5.
  3. Unreachable — the arccos is out of range, the IK returns NaN.
  4. The state machine’s health check detects the NaN (the IK’s failure), and the error recovery triggers — the arm stays at the safe pose, the pipeline reports the failure, and the human or the planner re-targets (a closer object, or a closer base).

The lesson. The unreachable target is the loud failure (the NaN from the arccos), and the health check detects it before the trajectory is generated. The fix is the re-target (a closer object) or the base’s relocation, and the arm never moves to the unreachable target (the safety is preserved).

WE-L.2.c — Faded: the branch discontinuity

Setup. The pipeline’s target is slightly changed (the object is moved by 1 cm), and the IK returns a q_grasp that differs from the previous one by 3.28 rad.

Question. Does the reachability check detect the change? Does the frame audit detect it? What is the symptom, and which component produced the jump?

You must produce the intermediate steps. The setup and the answer are given.

Answer. The reachability check passes (both targets are in the workspace). The frame audit passes (the transforms are correct). The IK returns a valid q_grasp for both targets — the poses are right, and the arm reaches the object either way. The symptom is ‖q_grasp − q_current‖ = 3.28 rad, and the trajectory from the current q to the new q_grasp swings the arm through a large motion — collision risk, velocity limit violation, dynamic infeasibility.

Which component produced the jump is the part the setup does not give you. It is not the 1 cm of object motion. Run the analytic IK — both branches computed, the nearer one selected — over a ±0.4 m box of targets and sweep 1 cm at a time: the maximum |q_grasp(k+1) − q_grasp(k)| is 0.000 rad. Nearest-branch selection is continuous, so no 1 cm move can make it jump. The 3.28 rad comes from the numerical solver: Newton’s method seeded at q_current converges to whichever branch its basin contains, and the basin boundary is not the branch boundary. Concretely, on the 2-link arm from q_start = (−0.90, 1.60), target (−0.20, 0.15) lands on q = (−2.889, 4.492) and target (−0.20, 0.16) — one centimeter away — lands on q = (−4.692, 1.752), a different branch, 3.28 rad away. Over a 6,000-target grid, 994 targets converge to the branch a nearest-branch selection would have rejected. Seeding at q_current is therefore not a branch-continuity guarantee; it is a heuristic that usually works.

The lesson. The branch discontinuity is the silent failure — the pose is correct, the IK is valid, but the joint-space motion is large. The cause is the solver’s basin, not the target’s motion, so the fix cannot be “move the object less”. The fix is the branch continuity’s health check (‖q_grasp − q_current‖ < δ), and the trigger is the retry (a different seed) or the fallback (a Cartesian path that keeps q on the current branch).

WE-L.2.d — Contrastive near-miss: the perception without the gate

Correct solution. The perception’s output is gated (the Mahalanobis gate from 0.3.4) — the outliers are rejected, and the pipeline retries.

Near-miss. The perception uses the raw detection (no gate), and the outlier (a bad T_Cam^Obj) is accepted — the object’s pose is wrong by a large amount (e.g., 0.3 m).

Why the near-miss produces a plausible but incorrect number. The IK solves the wrong pose and returns a valid q_grasp (the arm reaches the wrong position, but the IK converges). The controller tracks the wrong trajectory with a small error. The grasp misses (the object is not where the perception says), and the task fails — silently at the control level, visibly at the grasp. The symptom is the missed grasp, and the fix is the gate (the outlier is rejected, the pipeline retries).

The discrepancy, stated. The raw perception’s outlier is a large error (0.3 m), and the pipeline’s other stages do not detect it (the IK succeeds, the controller tracks, the state machine transitions). The gate is the only stage that detects the outlier, and its absence is the silent failure. The gate is the perception’s health check, and its output (the rejected detection) is the state machine’s signal for the retry.

Self-explanation 6. Before reading on: if the outlier is small (0.05 m), does the grasp still miss?

Answer. A small outlier (0.05 m) may be within the gripper’s tolerance (a large object’s fingers accept a 5 cm error), and the grasp succeeds — the outlier is small enough to be absorbed by the gripper’s compliance. The gate’s threshold α = 0.01 rejects the large outliers (the d² > 6.63) and accepts the small ones (the inliers), and the small outliers’ effect is bounded by the gripper’s tolerance. The choice of α is the trade-off (the false rejection vs. the outlier acceptance), and the small outliers are the accepted ones.

§L.2.3 — Comparative table: the three perception-to-IK checks

CheckPrecondition it verifiesTestWhat a failure looks likeHow loudWhat it misses
Frame audit — name cancellationThat the composition is well-typedAdjacent subscripts pair: T_X_Y @ T_Y_ZA broken chain, e.g. T_W_B @ T_EE_Cam with B ≠ EELoud on the line itselfA wrong matrix with a right name; a mixed convention (both conventions cancel)
Frame audit — data pointerThat each factor is the matrix it claimsCompare the value against the FK or the calibration, rotation as well as translationA T_EE_Cam holding the inverse: a plausible 0.1 m norm, a wrong rotationLoud if you have a reference valueThe calibration’s error — the reference is the calibration
ReachabilityThat a solution existsr ∈ [r_min, r_max], the arccos’s domain, the joint limitsNaN from the IK; r = 1.5 > r_max = 1.2 on the six-link armLoudNothing about which solution the solver returns
Branch selectionThat the solution is the near one‖q_grasp − q_current‖ < δA valid q_grasp 3.28 rad awaySilent without the checkNothing — but it is the only check that sees the jump

Design principle (P16.7). A check earns its place by the failure it is the only one to see. The reachability check is redundant with the IK’s own NaN — the solver already fails loudly — while the branch check is the only thing standing between a 1 cm object move and a 3.28 rad swing. Rank the checks by what they uniquely catch, and give the silent failures the explicit test, because the loud ones will announce themselves. The measured numbers are the argument: the analytic IK with nearest-branch selection is continuous over a ±0.4 m sweep at 1 cm resolution (maximum step 0.000 rad), so a 3.28 rad jump is never the target’s motion — it is the solver’s basin, and only a ‖Δq‖ gate sees it.

§L.2.4 — Concept check

Concept check (displaces M3, M2 in part). A learner says: “the IK returned a solution and it reaches the pose, so q_grasp is fine — branch selection is a nicety, and the frame audit is the perception’s problem anyway.”

Answer. The learner has failed to model the IK as a multi-valued map with a solver on top of it. The solution set has two branches for the two-link arm (q₂ = ±arccos(…)), both geometrically valid, and the arm must travel between q_current and whichever one is returned — travel the pose test says nothing about. A jump to the far branch is a large joint-space motion that can sweep the arm through the table or through the object. The bug class is a valid pose reached by a dangerous path: every pose check passes, the reachability passes, and the arm swings. The measurement that settles it: the jump is not proportional to the target’s motion — a 1 cm move produces 0.000 rad under nearest-branch selection and up to 3.28 rad under a Newton solve seeded at q_current, because the basin boundary is not the branch boundary. Seeding at q_current is a heuristic that usually works, not a guarantee; the explicit continuity gate is what makes it a guarantee. The second omission is the boundary’s ownership: the branch check and the frame audit are the same kind of object — a precondition verified at an interface — and calling the audit the perception’s concern is what leaves the IK receiving a target no one has checked.

§L.2.5 — Consolidation checkpoint 2

(a) You should now be able to: (i) run the name cancellation and state what it can and cannot catch; (ii) run the data-pointer check on a rotation as well as a translation, and say why a 0.1 m norm is not evidence; (iii) compute the reachability bounds from the link lengths at the point of use, and give the two-link arm’s r_max = 0.4 against the six-link arm’s 1.2; (iv) explain why nearest-branch selection is continuous and why a seeded Newton solve is not.

(b) Transfer probe. A colleague’s IK is seeded at q_current and the object moves 1 cm between two grasps; the returned q_grasp changes by 3.28 rad. They conclude the object moved too fast for the tracker and propose a higher camera frame rate. Is the diagnosis right, and what would you measure? Answer sketch: No. The jump is the solver’s basin, not the object’s motion: with the analytic IK and nearest-branch selection over a ±0.4 m box swept at 1 cm, the largest step is 0.000 rad, so no 1 cm move can produce this. On the 2-link arm from q_start = (−0.90, 1.60), target (−0.20, 0.15) lands on q = (−2.889, 4.492) and target (−0.20, 0.16) lands on q = (−4.692, 1.752) — and across a 6,000-target grid, 994 targets converge to the branch nearest-branch selection would reject. The measurement that settles it is to run both solvers over the same sweep and compare the step sizes; the fix is the continuity gate plus a different seed or a Cartesian path, not a faster camera.

(c) Remediation rule. If you cannot do (a)(iv) or (b), return to §L.2.1’s corollaries and WE-L.2.c, then redo (b) by running the analytic and numerical solvers over the same 1 cm sweep before attributing the jump.


Part C — The IK-to-Control Boundary

§L.3 — Objects: the trajectory’s feasibility, the feedforward, and the timing

The IK-to-control boundary has three checks:

  1. The trajectory’s feasibility — the q_d(t)’s peak acceleration max|q̈_d| must be within the actuator’s torque limit τ_max, given the inertia M(q) and the gravity g(q):

    • The peak torque τ_peak ≈ M · max|q̈_d| + max|g| must be ≤ τ_max.
    • The trajectory’s duration T_move is the variable — a faster trajectory (smaller T_move) has a larger q̈_peak and larger τ_peak, and the minimum T_move is the one where τ_peak = τ_max.
  2. The feedforward’s synchronization — the controller’s q̈_d must be the trajectory’s q̈_d (the same timestep), and the controller’s sample rate f_s must be fast enough for the trajectory’s bandwidth:

    • The trajectory’s dominant frequency f_traj ≈ 1/T_move (the duration’s reciprocal), and the controller’s f_s ≥ 100 · f_traj — the ω_n T_s ≤ 0.1 rule with ω_n = 10/T_move. The 10 · f_traj form is off by ten and is the one to watch for.
    • The trajectory’s q̈_d is computed at each t_k = k T_s, and the controller uses it as the feedforward.
  3. The tracking’s health check — the actual q’s error ‖e‖ = ‖q_d − q‖ must be within the tolerance ε_track before the grasp (the state machine’s transition), and the error’s growth beyond ε_track triggers the recovery (the fallback to a safe pose, or the retry with a slower trajectory).

§L.3.1 — Result: the trajectory’s duration T_move must be ≥ the minimum T_min = √(5.773 M |Δq| / (τ_max − g)), and the controller’s f_s must be **≥ 10 · (1/T_move)`; the two constraints are the coupling between the trajectory and the controller

Claim. For a minimum-jerk trajectory of duration T_move, the peak torque is τ_peak ≈ M · 5.773 |Δq| / T_move² + g, and the actuator’s limit gives the minimum duration T_min = √(5.773 M |Δq| / (τ_max − g)). The controller’s sample rate f_s must satisfy ω_n T_s ≤ 0.1 with ω_n ≈ 10/T_move (the controller’s bandwidth must exceed the trajectory’s 1/T_move by an order of magnitude for the tracking error to be small), giving T_s ≤ 0.01 T_move, i.e. f_s ≥ 100/T_move. The two together define the feasible (T_move, f_s) region — T_move ≥ T_min and f_s ≥ 100/T_move.

Proof sketch. The peak acceleration is 5.773 |Δq|/T_move², and the torque’s peak is the inertia’s M q̈_peak plus the gravity’s g. The actuator’s saturation τ_max gives the T_min. The controller’s ω_n must be larger than the trajectory’s bandwidth 1/T_move for accurate tracking; taking ω_n = 10/T_move and substituting into the ω_n T_s ≤ 0.1 rule from 1.1.3 gives T_s ≤ 0.01 T_move. Watch the units here, because the two common forms of the rule differ by 2π: f_s ≥ 10 ω_n/(2π) is the “10× the bandwidth in Hz” form, and combining it with ω_n = 10/T_move gives f_s ≥ 15.9/T_move, not 10/T_move. The 100/T_move above comes from the ω_n T_s ≤ 0.1 form, which is the stricter and unambiguous one. The two constraints are the coupling — the trajectory’s speed and the controller’s rate are linked.

Corollary. A faster trajectory (smaller T_move) requires a higher f_s and a higher K_p (the controller’s bandwidth), and the actuator’s saturation is the binding constraint at the fast end. A slower trajectory is easier (lower f_s, lower K_p), but the task’s duration is longer — the trade-off is the speed vs. the feasibility.

Corollary. The trajectory’s q̈_d must be the same as the controller’s feedforward — a mismatch (the trajectory’s q̈_d computed at a different rate, or with a different interpolation) biases the feedforward and increases the tracking error. The synchronization is the contract — the trajectory and the controller share the q_d, q̇_d, q̈_d at each t_k.

§L.3.2 — Worked-example ladder

WE-L.3.a — Fully worked: the feasible (T_move, f_s) region

Input. The running case’s 2-link arm (a₁ = a₂ = 0.2, M ≈ 0.05 kg·m² for the joint, g ≈ 0.5 N·m, τ_max = 10 N·m), |Δq| = 1 rad.

Trace.

  1. q̈_peak = 5.773 · 1/T_move² = 5.773/T_move².
  2. τ_peak = 0.05 · 5.773/T_move² + 0.5 = 0.2887/T_move² + 0.5.
  3. The actuator’s limit τ_peak ≤ 10: 0.2887/T_move² ≤ 9.5, so T_move² ≥ 0.0304, T_move ≥ 0.174 s.
  4. The controller’s rate f_s ≥ 100/T_move: at T_move = 0.174, f_s ≥ 575 Hz.
  5. The feasible region: T_move ≥ 0.174 s and f_s ≥ 100/T_move, so at T_move = 0.5 s, f_s ≥ 200 Hz; at T_move = 1 s, f_s ≥ 100 Hz.

Output and interpretation. The minimum duration is 0.174 s (the actuator’s limit), and the sample rate at that duration must be ≥ 575 Hz. For a slower T_move = 1 s, the f_s can be lower (100 Hz). The feasible region is the rectangle (T_move ≥ 0.174, f_s ≥ 100/T_move), and the task’s choice of T_move determines the minimum f_s.

Self-explanation 7. Before reading on: at T_move = 0.174 s (the minimum), the f_s = 575 Hz — is this a high or low rate for a robot controller?

Answer. 575 Hz is typical — a robot controller commonly runs at 1 kHz, and 575 Hz is the same order, which is the right conclusion and the one the 57.5 Hz figure in an earlier draft got wrong by a factor of ten. The rule is ω_n T_s ≤ 0.1; with ω_n = 10/T_move = 57.5 rad/s the sample time must be ≤ 1.74 ms, i.e. f_s ≥ 575 Hz. The factor of ten between 57.5 and 575 is exactly the 0.1 in the rule, and dropping it turns a comfortable-but-real requirement into one that looks trivially satisfied.

WE-L.3.b — Completion problem

Input. The same arm, |Δq| = 2 rad (a larger motion).

Trace (final 25% as prompts).

  1. q̈_peak = 5.773 · 2/T_move² = 11.546/T_move². ⟨your turn⟩
  2. τ_peak = 0.05 · 11.546/T_move² + 0.5 = 0.577/T_move² + 0.5. ⟨your turn⟩
  3. T_min: 0.577/T_min² ≤ 9.5, T_min ≥ √(0.0607) ≈ 0.246 s. ⟨your turn⟩
  4. f_s at T_min: ≥ 100/0.246 ≈ 407 Hz. ⟨your turn⟩

Answers.

  1. 11.546/T_move². ✓
  2. 0.577/T_move² + 0.5. ✓
  3. 0.246 s. ✓
  4. 407 Hz. ✓

The lesson. The larger |Δq| (2 rad vs. 1 rad) gives a longer T_min (0.246 vs. 0.174) and a lower f_s (407 vs. 575 Hz) — the larger motion is slower (the acceleration’s 1/T² is larger for the larger Δq), and the slower motion needs a lower sample rate. The trade-off is the motion’s size vs. the speed’s limit. Both numbers come from f_s ≥ 100/T_move; the 10/T_move form is the one that is off by ten.

WE-L.3.c — Faded: the trajectory–controller mismatch

Setup. The trajectory’s T_move = 0.2 s (near the minimum), and the controller’s K_p = 20 (the design for ω_n = 20 rad/s ≈ 3.2 Hz). The trajectory’s bandwidth 1/T_move = 5 Hz.

Question. Does the controller track the trajectory? What is the tracking error?

You must produce the intermediate steps. The setup and the answer are given.

Answer. The trajectory’s bandwidth (5 Hz) is larger than the controller’s (3.2 Hz), so the controller is too slow — the tracking error is large (the ω_n is less than the trajectory’s 1/T_move, and the error is forced by the trajectory’s acceleration). The error’s magnitude: e ≈ q̈_peak / ω_n² = (5.773 · 1/0.04)/400 = 144.3/400 ≈ 0.36 rad — a very large error (the arm lags by ~20°). The fix is a higher K_p (a faster controller), or a slower trajectory (a larger T_move).

The lesson. The trajectory’s bandwidth must be ≤ the controller’s ω_n/10 (the 10× rule), and a faster trajectory exceeds the controller’s capability. The symptom is the large lag, and the fix is the controller’s gains or the trajectory’s speed. The two must be designed together.

WE-L.3.d — Contrastive near-miss: the feedforward at a different rate

Correct solution. The trajectory’s q̈_d is computed at the same T_s as the controller, and the q̈_d(t_k) is the feedforward at the same timestep.

Near-miss. The trajectory’s q̈_d is computed at a finer T_s (e.g., 10× faster), and the controller subsamples it — the q̈_d at the controller’s t_k is the interpolated value, which lags the trajectory’s actual q̈_d.

Why the near-miss produces a plausible but incorrect number. The feedforward’s lag is O(T_s) (one sample’s delay), and the tracking error is O(T_s · q̈_d's rate) — a small but systematic error. For a slow trajectory, the error is negligible; for a fast trajectory (the q̈_d changes rapidly), the error is visible (the arm lags the trajectory by a constant offset). The symptom is the steady-state lag, and the fix is the synchronization (the same T_s for the trajectory and the controller).

The discrepancy, stated. The trajectory’s q̈_d at the finer T_s and the controller’s q̈_d at the coarser T_s differ by the interpolation’s error, which is O(T_s²) (the second-order interpolation) for a smooth trajectory and O(T_s) (the first-order) for a non-smooth one. The synchronization eliminates the interpolation’s error, and the two-rate design’s error is the price of the separation.

Self-explanation 8. Before reading on: in the near-miss, if the controller’s T_s = 1 ms and the trajectory’s T_s' = 0.1 ms (10× finer), what is the interpolation’s error for a minimum-jerk trajectory with T_move = 1 s?

Answer. The trajectory’s q̈_d changes on the scale of T_move = 1 s, and the interpolation over T_s = 1 ms is very accurate (the trajectory’s curvature over 1 ms is tiny). The error is O((T_s/T_move)²) = O(10⁻⁶) — negligible. The near-miss is only a problem for fast trajectories (small T_move) or coarse controllers (large T_s), where the ratio T_s/T_move is not small. The synchronization is necessary for the fast case, and optional for the slow one — the design should synchronize by default.

Self-explanation 9. Before reading on: the controller’s tracking error is 9.0e-4 rad at T_s = 1e-3 s, and raising K_p from 400 to 6400 leaves it at 9.3e-4 rad. At T_s = 1e-4 s the same 16× gain takes it from 5.55e-4 to 9.58e-5. Why does the gain do nothing at one sample rate and most of the work at the other?

Answer. It is two errors, not one, and the gain only touches the second. At T_s = 2.5e-4 the measured error is 3.22e-4 rad at K_p = 400 and 2.32e-4 rad at K_p = 6400 — a 16× gain buys 28%, so most of it is not gain-limited. The two terms are (i) a discretization floor from applying the feedforward q̈_d(t_k) once per step while q̈_d varies within the step, which scales as O(T_s) and is blind to K_p, and (ii) a bandwidth term ~ q̈_peak/ω_n², which is what K_p moves. The table makes the split visible: at T_s = 1e-3 the error is 9.0e-4 at K_p=400 and 9.3e-4 at K_p=6400 — flat, floor-dominated, the gain does nothing; at T_s = 1e-4 the same sweep runs 5.55e-4 → 9.58e-5, an 18× gain giving 5.8×, bandwidth-dominated. The floor’s size at the lab’s T_s = 2.5e-4 is around 3e-4 rad; below it no gain helps. So the diagnostic is not “does the error move when I raise K_p” but “at which T_s am I measuring”: a learner who tunes gains at a coarse T_s concludes the controller is broken, and a learner who tunes at a fine T_s concludes the gains are the whole story. Both are reading one term of a two-term sum.

§L.3.3 — Comparative table: the two IK-to-control constraints

ConstraintBinding quantityFormulaWhat sets itFailure if violatedSymptom
Trajectory feasibilityThe peak torque τ_peak against τ_max`T_move ≥ T_min = √(5.773 MΔq/(τ_max − g))`The actuator, the inertia, the motion’s size
Controller rateThe dimensionless product ω_n T_sf_s ≥ 100/T_move (from ω_n T_s ≤ 0.1 with ω_n = 10/T_move)The controller’s bandwidth against the trajectory’s 1/T_movePhase margin is lost; the loop oscillates near NyquistA bounded oscillation at the sampling frequency
Feedforward synchronizationThe q̈_d at each t_kThe same T_s for the trajectory and the controllerThe implementation, not the designThe feedforward is a lagged interpolation of the true q̈_dA small constant lag, O(T_s), visible only on fast trajectories
Tracking health check‖e‖ against ε_trackMeasured at the transition, before the graspThe task’s toleranceThe grasp proceeds from the wrong poseA missed grasp, with a clean tracking log

Design principle (P16.8). The trajectory’s duration and the controller’s rate are one design decision, not two. T_move sets q̈_peak = 5.773|Δq|/T_move², which sets both the peak torque and the bandwidth the controller must have; a faster trajectory is simultaneously closer to τ_max and further above the controller’s ω_n. The feasible region is T_move ≥ T_min and f_s ≥ 100/T_move, so choosing a duration silently chooses a minimum rate — and the 100 is not the 10 that a bandwidth-in-Hz reading suggests: ω_n T_s ≤ 0.1 with ω_n = 10/T_move gives T_s ≤ 0.01 T_move, and the “10× the bandwidth in Hz” form gives only 15.9/T_move. Use the dimensionless form, and state which one you used.

§L.3.4 — Concept check

Concept check (displaces M4). A learner says: “the trajectory is the path and the controller tracks whatever it is given — I’ll design the minimum-jerk profile first, then tune the gains to follow it.”

Answer. The learner has failed to model the coupling through the bandwidth: the controller is a low-pass system with ω_n, the trajectory is a signal with bandwidth 1/T_move, and tracking is good only when the second is well below the first. Designing the profile first fixes T_move and therefore fixes the controller’s required ω_n — the decision was made before the tuning began. On the lesson’s numbers, T_move = 0.2 s gives a 5 Hz trajectory against a controller designed at ω_n = 20 rad/s ≈ 3.2 Hz, and the error is q̈_peak/ω_n² = 144.3/400 ≈ 0.36 rad, about 20° of lag. The bug class is a trajectory that is dynamically infeasible for its own controller: no gain tuning fixes it without violating the stability margin, and the fix is to slow the trajectory or raise ω_n deliberately. There is a second trap inside the same symptom — at a coarse T_s the error is dominated by a discretization floor that is blind to K_p, so a learner who tunes gains there concludes the controller is broken when the sample rate is the constraint.

§L.3.5 — Consolidation checkpoint 3

(a) You should now be able to: (i) derive T_min from the peak acceleration and the actuator’s limit, and state what binds it; (ii) convert a chosen T_move into the minimum f_s with f_s ≥ 100/T_move, and say why the 10/T_move form is off by ten; (iii) explain why the trajectory’s bandwidth must sit an order of magnitude below the controller’s ω_n; (iv) separate the discretization floor from the bandwidth term in a measured tracking error.

(b) Transfer probe. A 2-link arm (a₁ = a₂ = 0.2, joint M ≈ 0.05 kg·m², g ≈ 0.5 N·m, τ_max = 10 N·m) must move |Δq| = 1 rad. Give T_min and the f_s it forces; then say what changes if the motion is 2 rad. Answer sketch: τ_peak = 0.2887/T_move² + 0.5 ≤ 10 gives T_move ≥ 0.174 s, and f_s ≥ 100/0.174 ≈ 575 Hz. For |Δq| = 2 rad the peak acceleration doubles, so T_min = √(0.577/9.5) ≈ 0.246 s and f_s ≥ 407 Hz — a larger motion takes longer and therefore needs a lower rate. The counterintuitive direction is the check: a learner who assumes bigger motions need faster sampling has inverted the 1/T² scaling. Note also r_max = 0.4 for this arm, not the six-link 1.2; a reachability check carrying 1.2 here would pass targets this arm cannot touch.

(c) Remediation rule. If you cannot do (a)(ii) or (b), return to §L.3.1’s proof sketch and WE-L.3.a, then redo (b) from ω_n T_s ≤ 0.1 rather than from a remembered constant.


Part D — The State Machine and the Error Recovery

§L.4 — Objects: the six stages, the transitions, and the recovery

The state machine has six stages:

  1. approach — move from q_current to the pre-grasp pose q_pre = q_grasp (the grasp’s pose), with a trajectory and the tracking.
  2. grasp — close the gripper (the force or the position control), and verify the grasp (the force threshold, the object’s presence).
  3. lift — raise the object (Δz = 0.1 m above the table), and verify the lift (the object is stable).
  4. move — move to the bin’s pre-place pose q_place, with the trajectory and the tracking.
  5. place — lower the object to the bin, open the gripper, and verify the release.
  6. retreat — retreat to a safe pose q_safe, and return to the idle state.

The transitions are the health checks:

  • approach → grasp: ‖e‖ < ε_track (the tracking is good).
  • grasp → lift: the gripper’s force > F_threshold (the grasp succeeded).
  • lift → move: the object’s presence is verified (the lift succeeded).
  • move → place: ‖e‖ < ε_track (the tracking is good).
  • place → retreat: the gripper is open (the release succeeded).

The error recovery is the fallback on failure:

  • approach fails (the target is unreachable or the tracking is poor): return to q_safe, report the failure.
  • grasp fails (the force is below the threshold): open the gripper, re-approach (a retry with a different pre-grasp, or a vision re-check), or abort.
  • lift fails (the object drops): re-approach (a retry) or abort.
  • move fails (the collision or the tracking is poor): return to q_safe, report the failure.
  • place fails (the release fails): re-attempt the release, or abort.
  • retreat fails: stay at q_safe (the safe pose is the final fallback).

§L.4.1 — Result: the state machine’s transitions are the pipeline’s safety mechanism, and the recovery is the primary path for the common failures

Claim. The state machine’s transitions are the health checks that prevent the pipeline from continuing on a failed stage, and the recovery is the fallback on failure. The recovery is the primary path for the common failures (unreachable targets, perception outliers, dropped objects), and the happy path is the exception in a real deployment.

Proof sketch. The transitions verify the stage’s success (the tracking error, the gripper’s force, the object’s presence) before the next stage. The recovery is the fallback — the q_safe pose, the retry, or the abort — and its frequency is the deployment’s reality: the unreachable targets, the perception outliers, and the dropped objects are common (not exceptions), and the recovery is the routine path.

Corollary. The state machine’s correctness is the pipeline’s correctness — a missing transition (no force check at the grasp) is a silent failure, and a missing recovery (no fallback on the unreachable target) is a safety issue. The checks are the design’s most important part, and the recovery is the design’s most tested part.

Corollary. The fallback q_safe is a pre-verified configuration (a collision-free, gravity-stable pose) that the arm can reach from any other pose, and it is the final recovery. The retry is the intermediate recovery (the re-approach, the vision re-check), and the abort is the human’s escalation — the three levels of recovery (the retry, the fallback, the abort) are the hierarchy of the error handling.

§L.4.2 — Worked-example ladder

WE-L.4.a — Fully worked: the state machine for a successful pick-and-place

Input. A target object, a reachable pose, a good perception.

Trace.

  1. idle → approach: the target is set, the IK succeeds, the trajectory is generated.
  2. approach → grasp: the tracking error ‖e‖ < 1 mm, the pre-grasp is reached.
  3. grasp → lift: the gripper’s force F > F_threshold, the grasp is verified.
  4. lift → move: the object is lifted (the z > 0.1 m), the lift is verified.
  5. move → place: the tracking error ‖e‖ < 1 mm, the bin is reached.
  6. place → retreat: the gripper is open, the release is verified.
  7. retreat → idle: the safe pose is reached, the task completes.

Output and interpretation. The happy path has six transitions, each with a health check. The checks are all satisfied, and the pipeline completes the task. The health checks are fast (the tracking error’s norm, the gripper’s force, the object’s presence) and reliable (the force is a direct measurement, the tracking is the controller’s output).

Self-explanation 9. Before reading on: in step 3, the grasp’s verification is the force (F > F_threshold). What if the object is soft (a sponge), and the force is small even when the grasp is successful?

Answer. The force threshold must be tuned to the object’s properties — a soft object needs a lower threshold (the sponge’s force is small even with a good grasp), and a hard object needs a higher threshold. The threshold’s choice is the trade-off (a low threshold misses the hard objects’ failed grasps, a high threshold falsely rejects the soft objects’ good grasps). The robust solution is a combined check (the force and the vision, or the force and the gripper’s position), and the single force is sufficient for a known object’s type.

WE-L.4.b — Completion problem

Input. The same pipeline, but the object is slightly beyond the arm’s reach.

Trace (final 25% as prompts).

  1. The IK fails (NaN from the arccos). ⟨your turn: which stage’s check detects the failure?⟩
  2. The state machine transitions: approach → error (the IK’s failure). ⟨your turn: what’s the recovery?⟩
  3. The recovery: return to q_safe, report the failure. ⟨your turn: what does the human do?⟩

Answers.

  1. The IK’s reachability check (the cos q₂’s range, or the NaN’s detection) — the approach stage’s entry.
  2. The state machine stays in the error state (the safe pose’s tracking), and the recovery is the fallback.
  3. The human re-targets (a closer object, or the base’s relocation), and the pipeline retries from the new target.

The lesson. The unreachable target is the loud failure, and the state machine’s error state catches it before the arm moves — the arm stays at the safe pose, and the human intervenes. The recovery is the planned path, not the exception.

WE-L.4.c — Faded: the dropped object

Setup. The lift stage, and the object drops (the gripper’s force drops to zero).

Question. Which health check detects the drop? What is the recovery?

You must produce the intermediate steps. The setup and the answer are given.

Answer. The health check is the force’s sudden drop (the F < F_threshold after the lift), or the object’s presence (the vision or the force’s zero). The recovery is the re-approach (the grasp retry from the current pose, or the vision re-check to find the object’s new position), or the fallback to q_safe if the object is not findable.

The lesson. The dropped object is a dynamic failure — the force is the signal, and the check must be continuous (not just at the transition). The recovery is the re-approach (a retry), or the fallback (if the object is lost). The continuous checks are the state machine’s refinement — the discrete transitions are necessary and not sufficient for the dynamic failures.

WE-L.4.d — Contrastive near-miss: the safe pose without verification

Correct solution. The q_safe pose is pre-verified (a collision-free, gravity-stable pose that the arm can reach from any other pose).

Near-miss. The q_safe pose is chosen arbitrarily (e.g., q = 0), without verifying that the arm can reach it without collision, and the fallback moves the arm through a collision with the table or the object.

Why the near-miss produces a plausible but incorrect number. The q = 0 is a valid configuration (the pose is legal), but the trajectory from the current q to q = 0 may collide with the environment (the arm’s sweep passes through the table’s workspace). The fallback moves the arm through the collision, and the safe pose is not safe — the symptom is the collision during the recovery, and the fix is the pre-verification (a collision model that checks the path from any q to q_safe, or a conservative q_safe above the table).

The discrepancy, stated. The q_safe’s validity (a legal configuration) and its reachability (a collision-free path from any q) are different properties, and the arbitrary q_safe is valid but not reachable. The fix is the reachability’s verification, and the pre-computed q_safe must be tested against the environment (the table, the objects) before it is used.

Self-explanation 10. Before reading on: if the q_safe is above the table (a high arm configuration), does the reachability still need the verification?

Answer. Yes — the q_safe above the table is less likely to collide with the table, but the path from the current q to q_safe may still pass through an object (a tall object, a wall) or exceed the arm’s limits (the high configuration’s joint limits). The pre-verification is needed even for the conservative q_safe, and the verification is the path’s collision-freeness, not just the endpoint’s.

§L.4.3 — Comparative table: the three levels of recovery

LevelTriggerActionWhat it costsWhen it is the right answerWhen it fails
RetryA recoverable stage failure — a force below threshold, a perception outlier, a dropped objectRe-approach with a different pre-grasp, a different IK seed, or a vision re-checkOne extra motion, a few secondsThe failure is local and the world has not changed irreversiblyThe cause is persistent — the same retry loops on the same miss
FallbackA failure that leaves the arm in an unknown or unsafe state — poor tracking, an unreachable target, a lost objectReturn to the pre-verified q_safeThe task’s progress, plus the travel to q_safeSafety must be restored before anything else is decidedq_safe was chosen arbitrarily: q = 0 is a legal pose whose path may cross the table
AbortA failure the pipeline cannot resolve — an unreachable object with no alternative, a hardware faultStop and escalate to the humanThe task, plus the operator’s timeThe next decision needs information the pipeline does not haveIt is used as a substitute for the two cheaper levels, and the pipeline stalls on routine failures

Design principle (P16.9). Recovery is a hierarchy, and each level is cheaper than the one above it. The retry is one extra motion; the fallback is the task’s progress; the abort is the operator’s time. Order the response by cost, and make each level’s trigger specific — a force below F_threshold is a retry, a NaN from the arccos is a fallback, an unreachable object with no alternative is an abort. A pipeline that routes every failure to q_safe is safe and useless; one that retries every failure loops. And verify q_safe as a path, not as a pose: q = 0 is collision-free as a configuration and can still sweep the arm through the table on the way there.

§L.4.4 — Concept check

Concept check (displaces M5). A learner says: “the state machine is a switch over the six stages, and the error handling is the catch block at the end — the happy path is the code that matters.”

Answer. The learner has failed to model the transition as a measurement, not a step counter. Each transition is a health check — ‖e‖ < ε_track, F > F_threshold, the object’s presence — and the check is what makes the next stage’s precondition true. Without it the machine is a sequence of motions that completes whether or not the grasp happened: the arm lifts nothing, carries nothing, and places nothing, with every pose correct throughout. The bug class is a silent task failure under a green state machine: the pose-level checks all pass and the task fails at the place stage with an empty gripper. And the recovery is not the exception path: unreachable targets, perception outliers, and dropped objects are routine, so the recovery is the code that runs most often and the one to test hardest. The force check at the grasp is the single highest-value transition, because the pose cannot certify a physical event.

§L.4.5 — Consolidation checkpoint 4

(a) You should now be able to: (i) list the six stages and the health check on each transition; (ii) order the three recovery levels by cost and give a trigger that belongs to each; (iii) explain why a tracking check cannot certify a grasp, and what can; (iv) state what makes q_safe safe — reachability from any pose, not just validity.

(b) Transfer probe. A soft object is grasped successfully, but the gripper’s force reads below F_threshold because the sponge deforms. The state machine’s grasp → lift transition fails and the pipeline routes to q_safe. Was the transition wrong, the threshold wrong, or the recovery wrong? Answer sketch: The threshold is wrong for this object — F_threshold was tuned on rigid objects, and a sponge transmits little force even under a good grasp. The transition fired correctly given the threshold, and the fallback executed correctly given the transition; the defect is upstream in the check’s calibration. The fix is per-object thresholds, or a combined check — force and gripper position, or force and vision — since either signal alone is ambiguous. The probe’s point is that “the recovery ran” is not evidence that the recovery was the right response: a false-positive health check consumes the task’s progress and teaches the operator to ignore the fallback, which is worse than a missed detection in a pipeline where recovery is routine.

(c) Remediation rule. If you cannot do (a)(ii) or (b), return to §L.4.1’s corollaries and WE-L.4.a, then redo (b) by naming which of the three levels each possible response belongs to before choosing one.


Part E — Failure Taxonomy, Lab, Cheat Sheet, Exercises, Exit Test

§L.5 — Failure taxonomy

SymptomThree most likely causes, rankedDiscriminating testFix
The arm reaches a valid pose but misses the object(1) Perception outlier (the T_Cam^Obj is wrong); (2) frame audit error (the composition’s sign); (3) calibration error (the T_EE^Cam is wrong)Check the object’s actual pose vs. the perceived; if different, it is (1) or (3)Gate the perception; audit the frames; re-calibrate
The arm swings through a large motion for a small target change(1) Branch discontinuity from the numerical solver’s basin; (2) joint limits forcing the far branch; (3) a genuine discontinuity in the target’s own motionSweep the target 1 cm at a time with the analytic IK and nearest-branch selection; if ‖Δq‖ stays small while the numerical solver’s does not, it is (1)Use the analytic branch selection, or gate on ‖Δq‖/‖Δx‖ — the seed alone does not prevent it
The arm lags the trajectory by a large amount(1) Trajectory too fast (1/T_move > ω_n/10); (2) controller gains too low; (3) feedforward mismatchCheck the trajectory’s bandwidth vs. ω_n; if larger, it is (1)Slow the trajectory; increase K_p; synchronize
The arm continues after a missed grasp(1) Missing health check at the grasp; (2) force threshold too low; (3) no object-presence verificationCheck the q after the grasp; if the object isn’t there, the check is missingAdd the force/presence check
The arm collides during the recovery(1) q_safe path is not collision-checked; (2) q_safe is unreachable; (3) the environment changedCheck the path to q_safe; if it collides, it is (1)Pre-verify the q_safe path
The object drops after a successful lift(1) Gripper force insufficient; (2) object’s weight exceeds the gripper’s capacity; (3) the object slippedCheck the force during the lift; if decreasing, it is (1) or (3)Increase the gripper’s force; slow the lift
The IK crashes (NaN) for a target(1) Unreachable; (2) singular configuration; (3) invalid input (a NaN in the perception)Check the target’s r vs. the workspace; if outside, it is (1)Saturate; gate the perception; handle the singularity

§L.6 — The lab

§L.6.1 — Task

Build the end-to-end pick-and-place pipeline for the running case (a simplified version with the 2-link sub-arm and a table), with the seven stages and the health checks, and demonstrate the recovery for at least two of the four pipeline failures.

§L.6.2 — Conditions

Python 3.10+, PyTorch 2.2+, CPU. Seed 42, dtype = float64. Runtime under 5 minutes. The 2-link arm (a₁ = a₂ = 0.2) with the FK from 1.1.1, the analytical IK from 1.1.2, the PD from 1.1.3, and the perception as a simulated T_Cam^Obj (with controllable errors). The table is at z = 0; the object is at z = 0.1. The health checks are the tracking error (< 1 mm), the reachability (|cos q₂| ≤ 1), the branch continuity (‖q_grasp − q_current‖ < 1 rad), and the grasp’s force (a simulated F > 0.1).

§L.6.3 — Controls held fixed

The seed, dtype, the arm, the table, the object, and the health checks’ thresholds are fixed. The failures are injected (unreachable target, perception outlier, branch jump, missed grasp) one at a time.

§L.6.4 — Metrics table

MetricDefinitionHow measured
pipeline_successWhether the object is placed in the binThe final state
failure_detectedWhether the health check catches the injected failureThe state machine’s transition
recovery_successWhether the recovery completes without collisionThe final state
end_effector_miss‖p_ee − p_target‖ at the graspThe tracking error
branch_jump‖q_grasp − q_current‖The IK’s output
tracking_error_maxThe maximum ‖e‖ during the trajectoryThe controller’s output

§L.6.5 — Pre-registered hypotheses

  • H1. The happy path (no injected failure) succeeds with pipeline_success = True and end_effector_miss < 1e-3. The prediction is the pair.
  • H2. The unreachable target is detected (failure_detected = True) and the arm stays at q_safe (no motion). The prediction is the pair.
  • H3. The perception error is silent only below 7.1e-4 m (the miss stays under the 1 mm tolerance), and is detected by some health check at and above it. The prediction is the pair: silent at 5e-4, detected at 1e-3.
  • H4. The branch jump (‖q_grasp − q_current‖ > 1 rad) is detected by the continuity check, and the recovery (a different seed) succeeds. The prediction is the pair.
  • H5. The missed grasp (the force below the threshold) is detected, and the recovery (a re-approach with a higher force) succeeds. The prediction is the pair. The force must be injected independently of the perception error: coupling them (force = 0.05 when the error >= 0.05) makes the check unreachable, because an error that large already fails the miss check and returns first.

§L.6.6 — The break-it sequence

  1. Unreachable target (loud). Set the target r = 1.5 > r_max = 0.4. Predicted symptom: the IK returns NaN; the state machine stays at q_safe; failure_detected = True. Actual: as predicted — loud.
  2. Perception outlier (loud or silent, by magnitude). Inject an error into T_Cam^Obj and sweep its magnitude. Predicted symptom: a large error is caught (by the reachability check or by the miss check) and a small one passes silently. Actual: as predicted, with a three-regime map measured on the 2-link arm (a₁ = a₂ = 0.2, target (0.2, 0), q_start = (−0.90, 1.60), miss = √2·e for a radial error e): below e = 7.1e-4 m the miss is under the 1 mm tolerance and the pipeline is SILENT; from 7.1e-4 m to 0.06 m the miss check catches it; from 0.06 m the perceived target crosses a branch boundary and the continuity check catches it; at 0.2 m the target leaves the workspace and the reachability check catches it. The silent band is narrow and it is a band, not a half-line — a 1e-3 m error already trips the miss check.
  3. Branch jump (silent). Drive the IK from the numerical solver, seeded at q_current, and step the target 1 cm across the basin boundary at (−0.20, 0.15) → (−0.20, 0.16). Predicted symptom: q_grasp changes by 3.28 rad (a different branch), the arm swings, and no health check fires except the continuity check. Actual: as predicted — silent at every stage before the continuity check (the pose is correct, the motion is large). With the analytic IK and nearest-branch selection the same 1 cm step produces 0.000 rad: the jump is the solver’s, and the fix is either the analytic branch selection or the continuity gate — not a smaller object motion.
  4. The continuity gate fires on smooth motion (the false positive). Keep the nearest-branch IK and move the target smoothly — no discontinuity anywhere — from (0.36, −0.12) outward, 1 cm at a time, from q_start = (−0.9, 1.6). Predicted symptom: the check fires at some point even though the joint path is continuous. Actual: as predicted — the smallest ‖Δq‖ that crosses the 1 rad threshold is 0.19 rad per step, at q = (−0.644, 0.644) → (−0.549, 0.471). A threshold on ‖Δq‖ per target step measures the step size as much as the discontinuity, so it must be stated per meter of target motion (‖Δq‖/‖Δx‖) or applied to the planned trajectory rather than to consecutive IK calls.

§L.6.7 — Acceptance criteria

CriterionThresholdArtefact it is tested on
H1’s successpipeline_success = True, end_effector_miss < 1e-3The happy path’s final state
H2’s detectionfailure_detected = True for the unreachableThe state machine’s log
H3’s perceptionSilent at 5e-4 m, detected at 1e-3 m; measured band edge matches MISS_TOL/√2 (measured 7.0e-4 vs predicted 7.1e-4)The sweep table’s outcome column and the band-edge search
H4’s branch jumpThe continuity check fires at a located branch discontinuity (measured 6.263 rad vs the 1 rad threshold), and the solver’s 1 cm jump of 3.28 rad also fires itThe discontinuity scan and the solver break-it
H5’s missed graspDetected at force = 0.05 <= 0.1, and the re-approach succeedsThe force check’s log
The state machine has all six stages6/6The code review
All four health checks are implemented4/4The code review

§L.6.8 — What good looks like

A strong submission has five properties:

  1. The seven stages are implemented as separate, testable functions, each with its input–output contract documented.
  2. The frame audit’s name cancellation and value check are both implemented, and the end-effector’s miss is demonstrated for a sign error (the inverse rotation or the wrong order).
  3. The four health checks (reachability, branch continuity, tracking, grasp) are implemented and demonstrated for each injected failure.
  4. The recovery is shown for at least two failures, with the safe pose’s pre-verification and the retry’s logic.
  5. The end-to-end success is measured (the object is placed in the bin) with the failure detection and the recovery counted.

§L.6.9 — Runnable lab code

# lab_1_1_4.py — the seven-stage pick-and-place pipeline with health checks
import math

A1 = A2 = 0.2                 # the two-link sub-arm
DT = 2.5e-4                   # control period; the tracking floor scales with it
T_MOVE = 1.0                  # minimum-jerk duration
BRANCH_TOL = 1.0              # rad, branch-continuity threshold
MISS_TOL = 1e-3               # m, end-effector miss
TRACK_TOL = 1e-3              # rad, joint tracking error
FORCE_MIN = 0.1               # simulated grasp force

def fk_2link(q):
    """Stage 1: forward kinematics, the pose of the tip in the base frame."""
    return (A1*math.cos(q[0]) + A2*math.cos(q[0]+q[1]),
            A1*math.sin(q[0]) + A2*math.sin(q[0]+q[1]))

def ik_2link(x, y):
    """Stage 3: both elbow branches, or None if the arccos leaves its domain."""
    c2 = (x*x + y*y - A1*A1 - A2*A2) / (2*A1*A2)
    if abs(c2) > 1.0:
        return None
    q2 = math.acos(c2)
    b = math.atan2(y, x)
    return [(b - math.atan2(A2*math.sin(q2), A1 + A2*math.cos(q2)),  q2),
            (b - math.atan2(-A2*math.sin(q2), A1 + A2*math.cos(q2)), -q2)]

def check_reachable(x, y):
    r = math.hypot(x, y)
    return abs(A1 - A2) <= r <= A1 + A2

def check_branch_continuity(q_new, q_cur, tol=BRANCH_TOL):
    return math.hypot(q_new[0]-q_cur[0], q_new[1]-q_cur[1]) < tol

def min_jerk(t, T, q0, qf):
    x = min(1.0, t / T)
    return q0 + (qf - q0) * (10*x**3 - 15*x**4 + 6*x**5)

def pd_step(q, qdot, qd, qd_dot, Kp=400., Kd=40., J=0.05, dt=DT):
    """Semi-implicit Euler: velocity first, then position with the NEW velocity.

    Forward Euler on this loop is unstable for dt > ~1 ms at Kp=400 (the pole
    moves outside the unit circle), which makes the tracking error non-monotone
    in dt and reads as a bug in the controller. Semi-implicit is stable at every
    dt in the table and the residual is first order in dt.
    """
    e, e_dot = qd - q, qd_dot - qdot
    qdot_new = qdot + (Kp*e + Kd*e_dot)/J*dt
    return q + qdot_new*dt, qdot_new

def pick_and_place(target_world, q_current, perception_error=0.0, force=0.5,
                   reapproach=False, verbose=True):
    """Stages 1-7. Returns (success, log). The target error is applied to both axes.

    `force` is injected independently of `perception_error` on purpose: the two
    were coupled in an earlier draft (force = 0.05 when error >= 0.05), which made
    the force check UNREACHABLE -- an error that large already fails the miss
    check (sqrt(2)*0.05 = 0.07 m >> 1 mm) and returns before the grasp is reached.
    A defect whose trigger cannot be reached is not a check.
    """
    log = []
    def say(s):
        log.append(s)
        if verbose: print("   ", s)

    # Stage 1-2: perception, with an injected error, then the frame audit.
    perceived = (target_world[0] + perception_error,
                 target_world[1] + perception_error)
    say(f"perception: perceived={tuple(round(v,4) for v in perceived)} "
        f"actual={target_world}")

    # Stage 3: reachability, then IK with branch selection.
    if not check_reachable(*perceived):
        say(f"FAIL[reachability]: r={math.hypot(*perceived):.3f} "
            f"outside [{abs(A1-A2)}, {A1+A2}] -- no motion")
        return False, log
    branches = ik_2link(*perceived)
    if branches is None:
        say("FAIL[ik]: arccos out of domain")
        return False, log
    q_grasp = min(branches, key=lambda q: math.hypot(q[0]-q_current[0], q[1]-q_current[1]))
    jump = math.hypot(q_grasp[0]-q_current[0], q_grasp[1]-q_current[1])
    if not check_branch_continuity(q_grasp, q_current):
        say(f"FAIL[branch]: |q_grasp - q_current| = {jump:.3f} rad >= {BRANCH_TOL}")
        return False, log
    say(f"ik: q_grasp={tuple(round(v,4) for v in q_grasp)}, branch jump={jump:.3f} rad")

    # Stages 4-5: minimum-jerk trajectory, PD tracking on BOTH joints.
    q, qdot = list(q_current), [0.0, 0.0]
    n = int(round(T_MOVE / DT))
    peak_track = 0.0
    for k in range(n + 1):
        t = k * DT
        qd = [min_jerk(t, T_MOVE, q_current[j], q_grasp[j]) for j in (0, 1)]
        qd_next = [min_jerk(t+DT, T_MOVE, q_current[j], q_grasp[j]) for j in (0, 1)]
        for j in (0, 1):
            qdot_d = (qd_next[j] - qd[j]) / DT
            q[j], qdot[j] = pd_step(q[j], qdot[j], qd[j], qdot_d, dt=DT)
            peak_track = max(peak_track, abs(q[j] - qd[j]))
    say(f"tracking: peak joint error = {peak_track:.2e} rad (tol {TRACK_TOL:.0e})")
    if peak_track >= TRACK_TOL:
        say("FAIL[tracking]")
        return False, log

    # Stage 6: the grasp, verified by the end-effector miss and the force.
    pos = fk_2link(q)
    miss = math.hypot(pos[0]-target_world[0], pos[1]-target_world[1])
    say(f"grasp: tip={tuple(round(v,4) for v in pos)}, miss={miss:.2e} m")
    if miss >= MISS_TOL:
        say(f"FAIL[miss]: {miss:.2e} m >= {MISS_TOL:.0e} m -- the object is not between the fingers")
        return False, log
    say(f"grasp: force={force}")
    if force <= FORCE_MIN:
        say(f"FAIL[force]: {force} <= {FORCE_MIN} -- the fingers closed on air")
        if not reapproach:
            return False, log
        force = 0.5
        say(f"recovery: re-approach with force={force}")
        pos = fk_2link(q)
        miss = math.hypot(pos[0]-target_world[0], pos[1]-target_world[1])
        if miss >= MISS_TOL:
            say(f"FAIL[miss after recovery]: {miss:.2e} m")
            return False, log
        say("recovery: grasp re-verified")

    say("SUCCESS: object lifted and placed")
    return True, log

# --- The runs -----------------------------------------------------------
TARGET = (0.2, 0.0)
Q_START = (-0.90, 1.60)

def run(label, target=TARGET, **kw):
    print(f"=== {label} ===")
    ok, _ = pick_and_place(target, Q_START, **kw)
    print(f"    success = {ok}\n")
    return ok

ok_happy = run("Happy path")
ok_unreach = run("Unreachable target", target=(0.5, 0.0))

print("=== Perception-error sweep (error applied to both axes) ===")
print(f"{'error':>8} {'perceived r':>12} {'branch jump':>12} {'miss (m)':>10}  outcome")
for e in (0.0, 5e-4, 1e-3, 1e-2, 2e-2, 5e-2, 0.1, 0.2):
    perceived = (TARGET[0]+e, TARGET[1]+e)
    r = math.hypot(*perceived)
    br = ik_2link(*perceived)
    jump = (min(math.hypot(b[0]-Q_START[0], b[1]-Q_START[1]) for b in br)
            if br else float('nan'))
    miss = math.sqrt(2) * e
    if not check_reachable(*perceived):
        outcome = "reachability"
    elif jump >= BRANCH_TOL:
        outcome = "branch jump"
    elif miss >= MISS_TOL:
        outcome = "miss"
    else:
        outcome = "SILENT"
    print(f"{e:>8.1e} {r:>12.4f} {jump:>12.3f} {miss:>10.2e}  {outcome}")

# --- Break-it: a 1 cm target move that flips the numerical solver's branch
def newton_ik(x, y, q0, iters=100):
    """Numerical IK seeded at q0 -- the branch it lands on is not the nearest one."""
    q = list(q0)
    for _ in range(iters):
        px, py = fk_2link(q)
        e = [x - px, y - py]
        if math.hypot(*e) < 1e-9:
            return q
        M = [[-A1*math.sin(q[0])-A2*math.sin(q[0]+q[1]), -A2*math.sin(q[0]+q[1])],
             [ A1*math.cos(q[0])+A2*math.cos(q[0]+q[1]),  A2*math.cos(q[0]+q[1])]]
        det = M[0][0]*M[1][1] - M[0][1]*M[1][0]
        if abs(det) < 1e-12:
            return None
        q = [q[0] + ( M[1][1]*e[0] - M[0][1]*e[1]) / det,
             q[1] + (-M[1][0]*e[0] + M[0][0]*e[1]) / det]
    return None

print("=== Branch jump: 1 cm in the target, 3.3 rad in joint space ===")
prev = None
for y in (0.15, 0.16):
    got = newton_ik(-0.20, y, Q_START)
    if prev is not None and got is not None:
        step = math.hypot(got[0]-prev[0], got[1]-prev[1])
        print(f"  target (-0.20,{y:.2f}): q=({got[0]:+.3f},{got[1]:+.3f}) "
              f"step={step:.2f} rad  continuity={'ok' if step < BRANCH_TOL else 'ABORT'}")
    else:
        print(f"  target (-0.20,{y:.2f}): q=({got[0]:+.3f},{got[1]:+.3f})")
    prev = got

# --- H3: locate the silent band by MEASUREMENT, not by restating the tolerance
# The sweep above prints an outcome per error. Find the largest error that still
# reports SILENT and the smallest that reports a failure, then check the band's
# upper edge against the predicted MISS_TOL/sqrt(2).
def outcome_at(e):
    perceived = (TARGET[0]+e, TARGET[1]+e)
    if not check_reachable(*perceived):
        return "reachability"
    br = ik_2link(*perceived)
    jump = min(math.hypot(b[0]-Q_START[0], b[1]-Q_START[1]) for b in br)
    if jump >= BRANCH_TOL:
        return "branch jump"
    return "miss" if math.sqrt(2)*e >= MISS_TOL else "SILENT"

grid = [i*1e-5 for i in range(0, 20001)]          # 0 to 0.2 m in 1e-5 steps
silent = [e for e in grid if outcome_at(e) == "SILENT"]
h3_edge = max(silent)
h3_pred = MISS_TOL/math.sqrt(2)
print(f"H3: measured silent band = error <= {h3_edge:.5f} m "
      f"(predicted {h3_pred:.5f}, residual {abs(h3_edge-h3_pred):.1e})")
print(f"H3: at 5e-4 -> {outcome_at(5e-4)}; at 1e-3 -> {outcome_at(1e-3)} "
      f"(predicted SILENT then detected)")

# --- H4: does the continuity check actually FIRE on a branch jump?
# The check is only meaningful at a point where the branch selection is
# discontinuous, so FIND that point rather than sweeping an arbitrary region:
# a sweep far from the seed trips on every sample and measures the region.
def nearest_branch(x, y):
    b = ik_2link(x, y)
    if b is None:
        return None
    return min(b, key=lambda q: math.hypot(q[0]-Q_START[0], q[1]-Q_START[1]))

flips = []
prev_q, prev_y = None, None
for i in range(0, 401):
    y = 0.30 - i*0.001
    q = nearest_branch(-0.05, y)
    if q is None:
        continue
    if prev_q is not None:
        step_y = math.hypot(q[0]-prev_q[0], q[1]-prev_q[1])
        if step_y > BRANCH_TOL:
            flips.append((prev_y, y, step_y))
    prev_q, prev_y = q, y
h4_max = max((f[2] for f in flips), default=0.0)
print(f"H4: nearest-branch discontinuities found: {len(flips)}")
for y0, y1, s in flips:
    print(f"  y={y0:.3f} -> {y1:.3f}: step {s:.3f} rad "
          f"(check {'FIRES' if s >= BRANCH_TOL else 'MISSES'})")
print(f"H4: largest {h4_max:.3f} rad vs threshold {BRANCH_TOL} rad; "
      f"the solver's 1 cm jump above is {step:.2f} rad and also trips it")

# --- H5: missed grasp detected, and recovery succeeds
ok_low, log_low = pick_and_place(TARGET, Q_START, force=0.05)
ok_rec, log_rec = pick_and_place(TARGET, Q_START, force=0.05, reapproach=True)
h5_detected = any("FAIL[force]" in line for line in log_low)
print(f"H5: low force detected = {h5_detected}; recovery succeeds = {ok_rec}")

# --- Summary ------------------------------------------------------------
print("\n--- lab 1.1.4 summary ---")
print(f"H1: happy path success = {ok_happy}")
print(f"H2: unreachable detected = {not ok_unreach}")
print(f"H3: measured band edge {h3_edge:.5f} m vs predicted {h3_pred:.5f}")
print(f"H4: {len(flips)} nearest-branch discontinuities, largest {h4_max:.3f} rad "
      f"vs threshold {BRANCH_TOL}")
print(f"H5: low-force detected = {h5_detected}; re-approach succeeds = {ok_rec}")

§L.7 — The cheat sheet

Term or resultStatement or definitionRole
PipelineThe seven stages from perception to recoveryIntroduced here; consumed by 1.2.1, 2.3.1, 4.2.1
Frame auditThe W←B←EE←Cam←Obj composition and its checksIntroduced here; consumed by every multi-frame pipeline
Reachability checkr ∈ [r_min, r_max] and the arccos’s domainIntroduced here; consumed by 2.3.1
Branch selectionClosest to q_current, within joint limitsIntroduced here; consumed by 2.3.1
Trajectory feasibility`T_move ≥ T_min = √(5.773 MΔq
Controller ratef_s ≥ 100/T_move (from ω_n T_s ≤ 0.1 with ω_n = 10/T_move)Introduced here; consumed by the real-time design
Feedforward synchronizationSame T_s for the trajectory and the controllerIntroduced here; consumed by the implementation
State machineSix stages: approach, grasp, lift, move, place, retreatIntroduced here; consumed by 2.3.1 (manipulation)
Health checksTracking error, gripper force, object presenceIntroduced here; consumed by 2.3.1
Error recoveryRetry, fallback to q_safe, abortIntroduced here; consumed by 2.3.1, 3.3.4 (safety)
The four pipeline failuresUnreachable, branch discontinuity, trajectory–controller mismatch, perception outlierIntroduced here; consumed by 2.3.1’s diagnosis
Design principle P16.1Correctness in isolation ≠ correctness in compositionIntroduced here; consumed by every pipeline
Design principle P16.2The frame audit spans every interfaceIntroduced here; consumed by every multi-frame pipeline
Design principle P16.3The branch continuity is a safety requirementIntroduced here; consumed by 2.3.1
Design principle P16.4The trajectory and the controller are designed togetherIntroduced here; consumed by 1.2.1
Design principle P16.5The recovery is the primary path, not the exceptionIntroduced here; consumed by 3.3.4
Design principle P16.6Every check certifies one assumption and inherits every otherIntroduced here; consumed by every pipeline’s check design
Design principle P16.7A check earns its place by the failure it is the only one to seeIntroduced here; consumed by 2.3.1’s diagnostics
Design principle P16.8The trajectory’s duration and the controller’s rate are one decisionIntroduced here; consumed by the real-time design
Design principle P16.9Recovery is a hierarchy; each level is cheaper than the one aboveIntroduced here; consumed by 3.3.4

§L.8 — Exercises with solutions

E 1.1.4.1 — The frame audit (diagnostic). A learner’s code has T_W_Obj = T_W_B @ T_B_EE @ T_EE_Cam @ T_Obj_Cam. What is the error?

S 1.1.4.1. The last factor is T_Obj_Cam, the pose of Cam in Obj, but the chain is T_W_B @ T_B_EE @ T_EE_Cam and ends at Cam, so the last factor must take Obj coordinates into Cam — T_Cam_Obj. Two things then go wrong, and only the first is the one usually described. The name check fires: the adjacent subscripts are Cam then Obj, which do not match, so the chain does not close. The value is not simply the inverse of the correct answer either — T_W_B @ T_B_EE @ T_EE_Cam @ T_Obj_Cam is T_W_Cam @ inv(T_Cam_Obj), which is not inv(T_W_Obj). On the running case’s numbers the correct translation is [0.4, 0.15, 0.8] and the wrong one is [0.19, 0.10, 0.20]; neither is the other’s inverse. So a learner who reasons “it is the wrong direction, so the result is the inverse” will get a plausible number from the wrong chain and an inverted comparison to match it. The lesson: a reversed subscript gives you a different transform, not a negated one, and “wrong direction” is a description of the subscript, not of the value.

E 1.1.4.2 — The reachability check (numerical). a₁ = a₂ = 0.2, target (0.5, 0.0). Is it reachable?

S 1.1.4.2. r = 0.5, a₁ + a₂ = 0.4, so r > a₁ + a₂ — unreachable. The cos q₂ = (0.25 − 0.04 − 0.04)/(2·0.04) = 0.17/0.08 = 2.125 > 1 — out of range. The check fails. The lesson: the reachability is the r ≤ a₁ + a₂ condition, and the cos q₂’s domain is the algebraic check.

E 1.1.4.3 — The branch continuity (numerical). q_current = (0, 0), q_grasp_A = (0.3, 0.5), q_grasp_B = (2.5, −1.0). Which branch is closer?

S 1.1.4.3. ‖q_grasp_A − q_current‖ = √(0.09 + 0.25) ≈ 0.58; ‖q_grasp_B − q_current‖ = √(6.25 + 1.0) ≈ 2.69. q_grasp_A is closer, and the branch continuity selects it. The lesson: the closest branch is the continuity’s choice, and the far branch is the swing’s source.

E 1.1.4.4 — The trajectory’s feasibility (numerical). M = 0.05, Δq = 1, τ_max = 10, g = 0.5. What is the minimum T_move?

S 1.1.4.4. T_min = √(5.773 · 0.05 · 1/(10 − 0.5)) = √(0.2887/9.5) = √0.0304 ≈ 0.174 s. The lesson: the minimum duration is the actuator’s limit, and the faster trajectory exceeds it.

E 1.1.4.5 — The tracking error (diagnostic). A trajectory with T_move = 0.1 s and a controller with ω_n = 20 rad/s. Does the controller track it?

S 1.1.4.5. The trajectory’s bandwidth 1/T_move = 10 Hz ≈ 62.8 rad/s, and the controller’s ω_n = 20 rad/s — the trajectory is 3× faster than the controller’s bandwidth. The tracking error is large (the controller cannot follow the trajectory’s acceleration). The fix is a higher ω_n (a faster controller) or a slower T_move. The lesson: the trajectory’s bandwidth must be ≤ the controller’s ω_n/10, and the mismatch is the large error’s source.

E 1.1.4.6 — The missed grasp (diagnostic). A pick-and-place pipeline reaches the object’s pose but the gripper’s force is zero. What is the likely cause, and what is the fix?

S 1.1.4.6. The grasp missed — the object is not between the gripper’s fingers (the pose is correct but the grasp is not). The cause is the perception’s error (the object is not where the pose says), the gripper’s misalignment, or the object’s motion. The fix is the grasp’s verification (the force check), the re-approach (a retry with a different pose), or the vision re-check (to find the object’s actual position). The lesson: the pose is necessary and not sufficient for the grasp, and the force is the grasp’s verification.

E 1.1.4.7 — The state machine (constructive, transfer probe). Write the transition logic for the approach → grasp transition, including the health checks.

S 1.1.4.7.

if ‖q_d(T) − q‖ < ε_track and ‖q̇‖ < ε_vel:
    transition_to("grasp")
else:
    transition_to("error", reason="tracking failure")

The check verifies the position (< 1 mm) and the velocity (< 0.01 rad/s) at the end of the approach, before the grasp. The failure triggers the error state. The lesson: the transition is the health check, and its failure is the recovery’s trigger.

E 1.1.4.8 — The recovery (constructive, transfer probe). Write the fallback logic for the grasp failure (the force is below the threshold).

S 1.1.4.8.

if force < F_threshold:
    open_gripper()
    retreat_to(q_safe)
    if retry_count < max_retries:
        retry_count += 1
        re_approach(target)  # or re-check perception
    else:
        transition_to("abort", reason="grasp failure after retries")

The fallback opens the gripper, retreats to q_safe, and retries (up to max_retries) before the abort. The q_safe is the pre-verified safe pose. The lesson: the recovery is a sequence (open, retreat, retry, abort), and the q_safe is the final safe state.


§L.9 — Exit test

You are ready for Module 1.2 (Robot Learning for Control) when you can, without notes:

  1. State the seven-stage pipeline and each stage’s input–output contract.
  2. State the four interfaces’ hidden assumptions and the four pipeline failures.
  3. Apply the frame audit to a multi-frame composition and detect the sign errors.
  4. State the reachability check (r ∈ [r_min, r_max], the arccos’s domain) and the branch continuity check.
  5. State the trajectory’s feasibility (T_move ≥ T_min) and the controller’s sample rate (f_s ≥ 100/T_move).
  6. State the feedforward synchronization and the mismatch’s symptom.
  7. State the six-stage state machine and the health checks.
  8. State the error recovery (retry, fallback, abort) and the q_safe’s pre-verification.
  9. Diagnose the unreachable target (loud NaN), the branch jump (large ‖q_grasp − q_current‖), the trajectory–controller mismatch (large lag), and the perception outlier (missed grasp) from their symptoms.
  10. State the grasp’s verification (force, presence) and why the pose is insufficient.
  11. State the q_safe’s pre-verification (collision-free path, reachable) and why an arbitrary q_safe is unsafe.
  12. State the recovery’s hierarchy (retry, fallback, abort) and the recovery’s frequency in real deployments.

If any item is not answerable without notes, return to the section that introduced it: 1 to §L.1, 2 to §L.1.1, 3 to §L.2, 4 to §L.2, 5 to §L.3, 6 to §L.3.1, 7 to §L.4, 8 to §L.4, 9 to §L.5, 10 to §L.4.2, 11 to §L.4.2, 12 to §L.4.1.


End of Module 1.1. The learner has now completed the classical robotics foundation: the FK (DH and PoE), the IK (analytical and numerical), the control (PID and computed torque), and the integration (the full pick-and-place pipeline with health checks and error recovery). The next module, 1.2 — Robot Learning for Control, replaces the hand-designed controller with a learned policy, using the same state, action, and reward structure that the classical pipeline’s stages define.

§L.10 — Provenance and evidence tiers

What this lesson reproduces. Every hypothesis in §L.6.5 is executed by the lab in §L.6.9. These claims are tier A, status R.

ClaimTierStatusWhere it is reproduced
The happy path completes: success = True, grasp miss 2.76e-5 mARH1
An unreachable target is detected and the arm does not moveARH2
The silent perception band’s edge is 7.0e-4 m, matching the predicted MISS_TOL/√2 = 7.1e-4ARH3’s band-edge search, measured rather than restated
A nearest-branch discontinuity exists and exceeds the 1 rad continuity threshold: 6.263 radARH4’s discontinuity scan
The numerical solver jumps 3.28 rad for a 1 cm target moveARThe solver break-it’s paired print
A missed grasp at force = 0.05 <= 0.1 is detected, and the re-approach succeedsARH5
The pipeline’s peak joint tracking error is 3.22e-4 rad against a 1e-3 toleranceARThe tracking line

The 1 rad threshold is calibrated on both sides. A threshold is only meaningful if it separates a genuine branch jump from ordinary motion, and that requires measuring both. Walking the target in 1e-4 steps through the reachable region, the largest step between consecutive nearest-branch configurations is 8e-4 rad; the discontinuity at the branch boundary is 6.263 rad; the solver’s 1 cm jump is 3.28 rad. The threshold sits between the two populations with roughly three orders of magnitude of margin on each side, which is what makes 1 rad a defensible value rather than a number that happened to work. The shipped lab measures the upper side (H4’s scan); the lower side was measured separately and is recorded here.

Three defects in this lab were found by running it, and are recorded here. First, the force check was unreachable: force was set to 0.05 when perception_error >= 0.05, but an error that large gives a 0.07 m miss, so the miss check returned before the force was ever read. The force is now injected independently. Second, H3’s summary line printed MISS_TOL/√2 — its own threshold — instead of the measured band edge, a gate that could not fail; the lab now searches the sweep for the edge. Third, H4’s first version swept a region far from the seed, where every target trips the check (2001/2001), which measures the region and not the check; it now locates the discontinuity.

No external sources are cited in this lesson. Every stage of the pipeline is built in Parts A–D from the earlier lessons’ components (FK from 1.1.1, IK from 1.1.2, control from 1.1.3). No paper is named.