Expose the planned reach alongside the commanded one - #1
Merged
Conversation
Once step() re-plans against the measured position, its output stops being a description of the reach and becomes a description of the reach plus whatever the effector did. That is the right thing to send to the effector and the wrong thing to record as ground truth, and there was no way to get the latter back out. Integrating the commanded velocity does not recover it. Each step's velocity is anchored to the effector rather than to the integral, so integrating a command issued to a lagging effector runs past the target and keeps going -- the command is correct for the effector's frame and meaningless outside it. plan_position and plan_velocity evaluate the reach as planned at the cue, on the original start-to-target axis, unconditioned on what happened. They are the same polynomial step() already uses for its ballistic phase, evaluated over the whole reach rather than only over ballistic_duration, so through that phase the command and the plan are the same function to machine precision. Afterwards they separate by exactly the correction re-planning applied, which makes their difference the effector's tracking error -- the useful part, and not recoverable from either signal alone. plan_position returns None for an inactive reach rather than the origin, because 0,0 is a real coordinate and cannot double as "no plan". step() can return 0,0 for no command since rest is the unambiguous zero of a velocity; position has no such zero. A caller wanting an unbroken signal substitutes the effector's own position -- with no reach, where it is is where it should be. plan_velocity does return 0,0, matching step(). Time outside the reach clamps rather than extrapolating, so a query before the cue gives the start point and one past the nominal duration gives the target. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Once
step()re-plans against the measured position, its output stops being a description of the reach and becomes a description of the reach plus whatever the effector did. That is the right thing to send to the effector and the wrong thing to record as ground truth, and there was no way to get the latter back out.Integrating the commanded velocity does not recover it. Each step's velocity is anchored to the effector rather than to the integral, so integrating a command issued to a lagging effector runs past the target and keeps going — the command is correct in the effector's frame and meaningless outside it.
What
Two new functions,
plan_position(state, t)andplan_velocity(state, t), giving the reach as planned at the cue, on the original start→target axis, unconditioned on what happened.step()already uses for its ballistic phase, over the whole reach rather than only overballistic_duration. Through that phase the command and the plan are the same function to machine precision.plan_positionreturnsNonefor an inactive reach rather than the origin, because(0, 0)is a real coordinate and cannot double as "no plan".step()can return(0, 0)for "no command" since rest is the unambiguous zero of a velocity; position has no such zero. A caller wanting an unbroken signal substitutes the effector's own position — with no reach, where it is is where it should be.plan_velocitydoes return(0, 0), matchingstep().Purely additive — no change to
step(),begin_reach(), orreset().Tests
New
TestNominalTrajectoryclass covering the endpoints, the canonical min-jerk curve, clamping, theNone-vs-origin distinction, independence from the effector's actual path,plan_velocityas the numerical derivative ofplan_position, exact agreement with the ballistic command, command and plan staying together for an undisturbed reach, and separating for a disturbed one.50 tests pass.
🤖 Generated with Claude Code