make motion utils reusable - #917
Conversation
🦋 Changeset detectedLatest commit: c8e86b9 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
863d2ac to
7ab4436
Compare
100f7d9 to
9b5556f
Compare
9b5556f to
b21fb5e
Compare
57e9ff3 to
c8e86b9
Compare
|
Claude (@claude) review |
|
Claude finished Devin T. Currie (@DTCurrie)'s task in 3m 40s —— View job Review
Clean refactoring. The structural reasoning is sound and the implementation is faithful to the PR description. Notes by severity: Nit
Observations (not blocking)
Correctness
One nit on the comment placement; everything else is correct. |
| * A step addresses joints positionally per component; a missing column reads as zero, where RDK's | ||
| * `FrameSystem.Transform` errors instead. | ||
| */ | ||
| // `offset` is in the column's unit, radians or mm, not degrees like the sibling `min`/`max`. |
There was a problem hiding this comment.
The // 'offset' is in... line floats between the JSDoc block and the function signature, which is an unusual position per the repo's code-comments.md rule ("inline rationale next to the code it explains"). The note is about mimic.offset specifically, so it reads better as an inline comment on the return line where it matters:
| // `offset` is in the column's unit, radians or mm, not degrees like the sibling `min`/`max`. | |
| export const jointValueAt = ( | |
| descriptor: JointFrameDescriptor, | |
| stepInputs: TrajectoryStep | |
| ): number => { | |
| const column = stepInputs[descriptor.componentName]?.[descriptor.jointIndex] ?? 0 | |
| const { mimic } = descriptor | |
| // `offset` is in the column's unit (radians or mm), not degrees like the sibling `min`/`max`. | |
| return mimic ? mimic.multiplier * column + mimic.offset : column | |
| } |
|
|
||
| import type { FrameSystemJson } from '../frameDescriptors' | ||
|
|
||
| import gantryPlan from '../../plugins/MotionPlanReplayer/__tests__/__fixtures__/gantry-plan.json?raw' |
There was a problem hiding this comment.
The fixtures use relative paths (../../plugins/MotionPlanReplayer/__tests__/__fixtures__/...) while parsePlan uses an absolute alias ($lib/plugins/MotionPlanReplayer/parse-plan). Mixing is fine functionally, but if the fixtures were co-located under $lib/motion/__tests__/__fixtures__/ the spec would be self-contained and the cross-directory relative hops would disappear. Not blocking — just noting it for the refactor stack if there's ever a dedicated fixture pass.
c8e86b9 to
df5e782
Compare
Moves the frame-system reconstruction out of the
MotionPlanReplayerplugin into$lib/motion, with no behavior change. Stacks on #913, against base branchfix/mimic-joints.The replayer builds a drawable chain out of RDK's
frame_systemdump. Nothing about that reconstruction is specific to a plan dump: it readsframesplusparents, and anything that can produce those can be drawn. Relocating it first means the behavior fixes stacked above this PR are readable as behavior fixes rather than as noise inside a rename.This PR must not merge without #918. The reason is the last entry under
### Why?.Stack
$lib/motionMoveFrameplugin (Motion plan preview #908)Frontend
MotionPlanReplayer/build-frame-descriptors.tsmoves tomotion/frameDescriptors.ts, andMotionPlanReplayer/model-joint-columns.tstomotion/jointColumns.ts. Both specs move with them. The fixtures stay under the plugin, becauseplan-to-snapshots.spec.tsstill reads them from there.buildFrameDescriptors,buildFrameContextsandbuildDescriptorstake a newFrameSystemJson(framesplusparents) instead ofParsedPlan.ParsedPlanis structurally a superset, so the replayer passes its parsed plan straight in and no call site changes.RawFrameandFrameSystemJsonare declared inframeDescriptors.ts.parse-plan.tskeeps its zod-derived equivalent, which is what validates the wire shape, but no longer exports it under the nameRawFrame.computeJointPoseandjointValueAtmove out ofplan-to-snapshots.tsinto a newmotion/jointPose.ts, along with aTrajectoryStepalias thatdescriptorToTransformnow uses.parse-plan.ts,plan-to-snapshots.tsandworld-state-obstacles.tsare repointed at the new paths.[motion]rather than[MotionPlanReplayer]. The three in code that did not move keep their old prefix.Nothing consumer-facing moves:
plugins/index.tsstill exportstransformBytesToSnapshots,useMotionPlanReplayerandResolvePlanSnapshotswith the same signatures.$lib/motionis in no barrel and nopackage.jsonexport path, so none of it is reachable from outside the package.Why?
Why split the move from the fixes that follow it?
build-frame-descriptors.tshas two real bugs in it, joint columns read in declaration order rather than RDK's schema order, and geometry that fails to decode, and both are fixed in the PRs above this one. Landing a fix inside a rename means a reviewer has to tell "this line moved" from "this line changed" by eye, across a 400 line file. Doing the move alone first makes git pair the files as renames and reduces the next three PRs to the lines that actually changed.Why
FrameSystemJsonrather than keepingParsedPlan?ParsedPlanalso carriestrajectory,goals,worldStateandobstaclesInWorldFrame, none of which this file reads. Depending on it meant$lib/motionwould import from$lib/plugins, inverting the layering. Because TypeScript is structural, narrowing the parameter costs nothing at the call sites.Why a separate
jointPose.tsinstead of leaving the kinematics inplan-to-snapshots.ts?plan-to-snapshots.tsis about assemblingSnapshotmessages.computeJointPoseandjointValueAtare the kinematics underneath that, and nothing in them knows a plan is being replayed: they turn one joint value into one pose. Separating them is what lets the PRs above this one run the same kinematics for a previewed move rather than a replayed plan.Why do half the diagnostics keep the old prefix?
Because half the code did not move.
plan-dropper.ts,useMotionPlanReplayer.svelte.tsandworld-state-obstacles.tsstayed in the plugin and are structurally replayer only: a failed hostresolvePlanSnapshotscallback, aloadPlanerror, and aworld_statekey that only a plan dump carries. Relabeling those to[motion]would cost attribution for nothing, and would read as ambiguous once a second feature in this stack starts emitting into the same console.Why can this not merge without #918?
It relocates the joint column mapper without changing its behavior, so #913's constraint travels with it: the mapper numbers
model.jointsin declaration order, which is the wrong order for a branching model such as a two finger gripper. #918 replaces that numbering with the schema walk, andjointColumns.tsis byte identical from #918 to the top of the stack.Testing
Ran
pnpm exec vitest --run: 70 test files, 712 tests, all passing, which is the same tuple as onfix/mimic-joints.pnpm exec svelte-check --tsconfig ./tsconfig.jsonreports 0 errors and 0 warnings.This PR adds and removes no tests by design. The spec diff contains no added or removed
itortestcall site, and both branches carry 70 spec files and 616 test call sites, so the delta is zero by construction rather than by coincidence. If the relocation changed behavior, an existing test should be the thing that says so.An unchanged count is necessary but not sufficient, since a behavior change nothing covers would not move it. The stronger check is a normalized diff, substituting the renamed identifiers in
frameDescriptors.tsback to their old spellings and diffing that againstbuild-frame-descriptors.ts. What survives is the header docblock, theRawFrameandFrameSystemJsondeclarations that replace theParsedPlanimport, and oneconsole.warnthat prettier now fits on a single line because the prefix got shorter. No executable statement differs.jointColumns.tsis byte identical tomodel-joint-columns.ts,computeJointPosemoves with its body unchanged, andjointValueAtis the old inline block withmimicdestructured out, which no input distinguishes.