Skip to content

Commit 705adcc

Browse files
committed
apply comment, test and description review for #930
1 parent ea1a3bf commit 705adcc

3 files changed

Lines changed: 17 additions & 82 deletions

File tree

src/lib/plugins/MoveFrame/MovePreview.svelte

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,6 @@
1818
const planning = $derived(preview.status === 'planning')
1919
const ready = $derived(preview.status === 'ready')
2020
21-
/**
22-
* Named for what a frame *is*, not for how processed it looks.
23-
*
24-
* "Raw / Smoothed" read as honest-versus-prettified, which is backwards: nothing is eased or
25-
* rounded, the same path is just sampled more finely. Waypoints leads because it is the plan
26-
* exactly as returned.
27-
*/
2821
const detailLabels: Record<PreviewDetail, string> = {
2922
waypoints: 'Waypoints',
3023
interpolated: 'Interpolated',
@@ -94,11 +87,6 @@
9487
{/snippet}
9588
</ToggleButtons>
9689

97-
<!--
98-
The labels alone cannot carry this, so the counts do. Seeing "2 frames" against "182
99-
frames" for the same move is the fastest way to understand what the setting changes,
100-
and it makes a sparse plan's sparseness impossible to miss.
101-
-->
10290
<p class="text-subtle-2">
10391
{#if preview.detail === 'waypoints'}
10492
{frameCount} frames — one per configuration the planner returned, and nothing between.

src/lib/plugins/MoveFrame/__tests__/usePreviewMove.svelte.spec.ts

Lines changed: 8 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,11 @@ import {
1818
} from './__fixtures__/previewMoveHarness.svelte'
1919

2020
const dump = parsePlan(planJson)
21-
/**
22-
* The rig `plan-gantry.json` (`interpolateTrajectory.spec.ts`) was lifted from: a 40 mm prismatic
23-
* slide, captured rather than hand-written so the joint is genuinely `type: "prismatic"` in the
24-
* model JSON and not just labelled that way by a test double.
25-
*/
2621
const gantryDump = parsePlan(gantryPlanJson)
2722

2823
/**
29-
* A part's `kinematics` carries the same `ModelConfigJSON` a dump nests under
30-
* `frames[partName].frame.model`, so a realistic frame system can be lifted out of a fixture instead
31-
* of hand-written — geometry, joint chain and all.
24+
* A part's `kinematics` is the same `ModelConfigJSON` a dump nests under `frames[part].frame.model`,
25+
* so a fixture yields a realistic frame system: geometry, joint chain and all.
3226
*/
3327
const kinematicsFromDump = (source: ParsedPlan, partName: string): Struct => {
3428
const entry = source.frames[partName]
@@ -65,23 +59,15 @@ const SHAPELESS = part(
6559
} as never)
6660
)
6761

68-
/**
69-
* One prismatic joint, real rather than hand-labelled: `gantry-plan.json`'s model has a `carriage`
70-
* link riding a `type: "prismatic"` joint, so a descriptor built from it is what `jointMotionsOf`
71-
* would actually see off a machine, not a stand-in built to say `'translational'`.
72-
*/
62+
/** A captured `type: "prismatic"` joint, not a double hand-labelled `'translational'`. */
7363
const GANTRY = part('gantry-1', kinematicsFromDump(gantryDump, 'gantry-1'))
7464

7565
/** Two distinct configurations, so it never reads as "already at the goal". */
7666
const PLAN_REPLY: JsonValue = {
7767
plan: [{ 'left-arm': [0, 0, 0, 0, 0, 0] }, { 'left-arm': [1, 0, 0, 0, 0, 0] }],
7868
}
7969

80-
/**
81-
* The same 40 mm slide `gantry-plan.json` captures: `gantry-1` moving from 50 to
82-
* 90.00000000000001, everything else held. Read as radians instead of millimetres, that stroke is
83-
* over 2291°, which is the gap `interpolatedFrames`'s `motions` argument exists to close.
84-
*/
70+
/** The 40 mm slide `gantry-plan.json` captures. Read as radians rather than millimetres it is 2291°. */
8571
const GANTRY_SLIDE: JsonValue = {
8672
plan: [{ 'gantry-1': [50] }, { 'gantry-1': [90.00000000000001] }],
8773
}
@@ -303,13 +289,8 @@ describe('where the ghosts stand before anything is scrubbed', () => {
303289
})
304290

305291
/**
306-
* The hook keeps two arrays and they answer different questions. `trajectory` is what the planner
307-
* said and the only thing `execute` may be handed; `playbackFrames` is that same motion subdivided
308-
* for the scrubber, which the robot must never be asked to run.
309-
*
310-
* Nothing had ever told them apart: returning the playback frames from `get trajectory()` — handing
311-
* the robot the interpolated ones — passed the whole suite. Subdividing is what makes the two
312-
* observably different, so this is the first PR in which the distinction can be pinned at all.
292+
* `trajectory` is the only thing `execute` may be handed; `playbackFrames` is that same motion
293+
* subdivided for the scrubber, which the robot must never be asked to run.
313294
*/
314295
describe('what the scrubber walks', () => {
315296
it('plays one frame per configuration the planner returned', async () => {
@@ -388,10 +369,6 @@ describe('the request the panel sends', () => {
388369
})
389370
})
390371

391-
/**
392-
* `waypointIndices` is what the scrubber draws its tick marks from, and it is the only thing telling
393-
* a user which of 180 interpolated frames the planner actually chose.
394-
*/
395372
describe('marking which played frames are planned waypoints', () => {
396373
it('marks every frame when each one is a waypoint', async () => {
397374
const h = setup()
@@ -417,11 +394,6 @@ describe('marking which played frames are planned waypoints', () => {
417394
})
418395
})
419396

420-
/**
421-
* The two detail settings are different framings of one motion, so a frame index does not carry
422-
* across. Leaving `currentStep` where it was pointed the scrubber past the end of the shorter
423-
* framing and left the ghosts showing a pose from the other one.
424-
*/
425397
describe('switching what a frame represents', () => {
426398
it('restarts playback rather than keeping an index that no longer means anything', async () => {
427399
const h = setup()
@@ -440,12 +412,8 @@ describe('switching what a frame represents', () => {
440412
})
441413

442414
/**
443-
* `interpolatedFrames` costs a joint's travel in degrees unless told otherwise, so a component the
444-
* hook does not label as prismatic has its millimetres read as radians — a 40 mm slide costs the
445-
* same as 40 radians of arm travel, over 200 turns. `jointMotionsOf(descriptors)` is what supplies
446-
* that label; `interpolateTrajectory.spec.ts` measures the unlabelled cost of this exact slide at
447-
* 1,529 frames against 10 labelled, so anything under 50 here is only reachable with the label
448-
* wired through.
415+
* Where the 50 comes from: without `jointMotionsOf`'s labels, `interpolateTrajectory.spec.ts`
416+
* measures this exact slide at 1,529 frames against 10 labelled.
449417
*/
450418
it('keeps a gantry slide within its millimetre budget instead of costing it in radians', async () => {
451419
const h = setup([GANTRY])

src/lib/plugins/MoveFrame/usePreviewMove.svelte.ts

Lines changed: 9 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -44,25 +44,14 @@ import {
4444
export type PreviewStatus = 'idle' | 'planning' | 'ready' | 'already-at-goal' | 'error'
4545

4646
/**
47-
* What one frame of playback represents. Not a fidelity setting — both show the same motion, and
48-
* neither is faster or slower than the real move, since a trajectory carries no timing at all.
49-
*
50-
* - `waypoints` gives each configuration the planner returned its own frame, and nothing between.
51-
* - `interpolated` adds frames along the straight joint path between waypoints, in proportion to
52-
* travel. That path is not invented — it is the one RDK collision-checks when it validates the
53-
* segment — but it is what the planner *approved*, not a promise of what the arm traces: the
54-
* component is handed the whole waypoint list so it can blend between them.
47+
* What one frame of playback represents: `waypoints` gives each configuration the planner returned
48+
* its own frame, `interpolated` fills in along the straight joint path RDK collision-checks.
5549
*/
5650
export type PreviewDetail = 'waypoints' | 'interpolated'
5751

5852
/**
59-
* How long a preview takes to play, whatever it is made of. Pacing to a duration rather than to a
60-
* frame rate is what makes the two detail settings comparable: `waypoints` and `interpolated`
61-
* describe the same motion with wildly different frame counts, so a fixed per-frame interval would
62-
* race through a two-waypoint plan and crawl through a two-hundred-waypoint one.
63-
*
64-
* It is also the honest unit here. A trajectory carries no timing, so no frame rate is more correct
65-
* than another — but "the whole move takes about this long" is at least a consistent claim.
53+
* How long a preview takes to play, whatever it is made of. Pacing to a duration rather than a frame
54+
* rate keeps a two-waypoint plan and a two-hundred-waypoint one comparable.
6655
*/
6756
const PREVIEW_DURATION_MS = 4000
6857

@@ -129,20 +118,10 @@ export const usePreviewMove = ({
129118
}: PreviewMoveOptions): PreviewMove => {
130119
let status = $state<PreviewStatus>('idle')
131120
let message = $state<string>()
132-
// Raw: all three are replaced wholesale — a new array assigned outright, never mutated in place —
133-
// so the fine-grained reactivity `$state`'s proxy buys elsewhere would go unused here. `trajectory`
134-
// and `playbackFrames` are also large, and nothing in this file reads into either of them
135-
// reactively; only `playbackFrames.length`, through the player. `waypointIndices` is smaller — one
136-
// entry per waypoint, not per frame — and it is read into: `MovePreview.svelte` hands it to
137-
// `TrajectoryScrubber` as `markers`, which maps, iterates and length-compares it directly. That
138-
// reads elements of a wholesale-replaced array, which `$state.raw` still serves correctly; it is
139-
// only element-level *mutation* that a plain array would miss.
140-
//
141-
// Three arrays rather than one because they answer different questions. `trajectory` is what the
142-
// planner said and what `execute` must receive; `playbackFrames` is that same motion subdivided for
143-
// playback, which the robot must never be asked to run; `waypointIndices` marks which entries of
144-
// `playbackFrames` are planner waypoints rather than frames interpolated in between.
121+
// Raw: all three are replaced wholesale rather than mutated, so the deep proxy would go unused,
122+
// `waypointIndices` included — the scrubber reads it element-wise but never writes into it.
145123
let trajectory = $state.raw<TrajectoryStep[]>([])
124+
// What the scrubber walks. Only `trajectory` may ever be handed to `execute`.
146125
let playbackFrames = $state.raw<TrajectoryStep[]>([])
147126
let waypointIndices = $state.raw<number[]>([])
148127
let detail = $state<PreviewDetail>('waypoints')
@@ -340,8 +319,8 @@ export const usePreviewMove = ({
340319
get detail() {
341320
return detail
342321
},
343-
// Switching detail re-frames the same plan, so playback restarts rather than trying to map
344-
// the current frame across two different framings of the motion.
322+
// A frame index does not carry across the two framings, so playback restarts rather than
323+
// trying to map it.
345324
set detail(next: PreviewDetail) {
346325
if (next === detail) return
347326
detail = next

0 commit comments

Comments
 (0)