Force-Based Movements — §2 / §3 starting point
Draft for the two unfinished sections of the Notion RFC. Deliberately thin — the details should come from the fuzzing data, not from guessing ahead of it.
Figures live in force-based-movements-rfc/ as fuzzing-target-{classify,correct}.svg (with .png
renders alongside). One per section — the classify target under §2, the correct target under §3.


2. Target F/T profile motions — the fuzzing sweep
Today’s examples (button press GOOD/BAD, tip-pickup offsets) were collected by hand, one mutation at a time. The generalization is a waypoint fuzzing script: perturb the target waypoint by a sampled offset, run the production motion unchanged, record the F/T profile, and score the outcome independently (optical fork / camera / retry).
Picture it as a target. The center is the calibrated waypoint; each pip is one fuzzed attempt. Every pip carries an F/T profile, and every pip is labelled success or failure by something other than force.
What comes out:
- A set of labelled (offset → F/T profile) pairs, per primitive.
- A boundary separating success from failure — which is the calibration tolerance of that primitive, expressed in mm. We don’t currently know that number for anything.
- The reference profile for a good operation, measured rather than assumed.
Classification is then: given a profile, which side of the boundary is it on. Start simple — the existing studies suggest the signal lives in one or two specific channels rather than aggregate magnitude, so look there before reaching for anything fancy.
Sampling the fuzz. Rather than assuming a distribution, configs/*/calibrated/*.json in git
history is a record of every recalibration the fleet has had. The deltas between successive
recalibrations tell us what real drift looks like — magnitude, direction, spread — and the sweep
can be sampled from that. No deck time needed.
3. F/T local policy
Same target, read backwards.
§2 asks which side of the boundary is this profile on. §3 asks which way is home: given the F/T profile from a failed attempt, output a nudge to the waypoint, retry there, and repeat until the operation succeeds or the loop gives up.
π : F/T profile → offset correction
Tasks consume π on retry. The motion itself doesn’t change mid-flight — the next attempt is informed by what the last one felt. That keeps it out of real-time control territory: it reads a completed motion’s profile and acts on the following attempt.
Why this looks tractable. The ml1-011 offset sweep already points this way: Tx tracked
commanded dx at R² 0.98 over ±1 mm, with each 0.3 mm step separable from good. Inverted, that is
π for one primitive on one deck. §2’s sweep is what tells us how far that generalizes.
Guardrails (thin for now, fill in from data): clamp the nudge size, cap the retries, escalate rather than keep nudging if it isn’t converging, and report a converged correction as evidence for recalibration rather than silently writing it back to config.
FTProfile, in practice
The samples already cross the wire — ExecuteServoTrajectoryResponse carries
repeated ForceTorqueValues, handed to the MCAP writer at denso_controller_client.py:195 and
then dropped at :202 for a bare APIState. So this starts with returning a typed motion result,
not with new transport.
@dataclass(frozen=True)
class FTProfile:
motion: str
samples: Sequence[FTSample] # raw series
keyframes: Mapping[str, FTKeyframe] # wrench + pose at touchdown / seat / release
reference: Wrench | None # out-of-contact read at the same pose
class FTPolicy(Protocol):
def evaluate(self, profile: FTProfile) -> Ok | OffsetFault | UnknownFault: ...Three verdicts, not two: OffsetFault carries a correction to retry with, UnknownFault
escalates. Collapsing them is what would let the policy “correct” a bent tip.
How tractable is this with the sensor we have?
Tx responds at 0.90 N·m/mm against σ ≈ 0.071 N·m on Mx, so single-shot: 0.3 mm → 3.8σ
(resolvable, and it matches the observed result), 0.1 mm → 1.3σ (not). But the seat is a
hold, not a transient — averaging 25 samples (0.5 s at 50 Hz) cuts σ 5× and puts 0.1 mm at ~6σ,
for free. DC drift is the error that doesn’t average away, so the same-pose reference read matters
more than the noise floor does.
Before pricing a better sensor, separate σ_static (hold still, sample N times) from σ_reapproach (re-seat M times at the same commanded offset). Within-block σ in the offset study was 0.04–0.1 N·m, right at the noise floor — but if σ_reapproach dominates, the limit is mechanical repeatability and a better sensor buys nothing.
Open questions
- Does a correction learned on one deck/holder/tip transfer to another?
- Where does the linear-looking response stop being linear?
- Multichannel: the missing-tip study couldn’t resolve per-channel state from the wrist sensor, so 8-channel corrections may not be recoverable. Single-channel first.
- Not every failure is a position error (bent tip, debris, wrong labware). Needs a way to tell “offset” from “something else entirely” before nudging.
- What’s the right reference read, given the seat wrench carries a pose-dependent offset?
Related
- Force based movements RFC · Force based movements - working doc · ft-analysis-process
- darpa-edge-workstream-recommendation — why the threshold work (#1) lands first