From 726be2bcaea8c5768c1e4d6846a4d9d90dc361d5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 25 Jul 2026 12:26:55 +0000 Subject: [PATCH 1/6] Declare the MSG gripper's built-in camera mount The MSG carries an integrated camera mount, so its ToolConfig now ships a default CameraSpec. The video device stays per-machine: the spec's no-camera sentinel is resolved through the tool's runtime camera override, and frontends can use the declaration to surface the mount (e.g. hand-eye calibration guidance). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_015HkPS4EgPZg7tvgCxBYqTT --- parol6/tools.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/parol6/tools.py b/parol6/tools.py index c24f9fc..6f94c26 100644 --- a/parol6/tools.py +++ b/parol6/tools.py @@ -677,6 +677,9 @@ def _make_tcp_transform( transform=_make_tcp_transform(x=-0.029, z=-0.103), meshes=_MSG_100_MESHES, motions=_MSG_100_JAW_MOTION, + # The MSG carries a built-in camera mount; the video device is + # per-machine, supplied at runtime via the tool's camera override. + camera_spec=CameraSpec(), variants=( ToolVariant( key="100mm", From 7d8869c2b1505a5a17e26f310d2636317b1bfc88 Mon Sep 17 00:00:00 2001 From: jepson2k <55201008+Jepson2k@users.noreply.github.com> Date: Mon, 27 Jul 2026 13:40:32 -0400 Subject: [PATCH 2/6] Gate trajectory settling on progress, not elapsed ticks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The settle phase completed a segment after a fixed 20-tick cap even with the firmware still closing on the target, and the mock transport freezes all residual motion once the command stream goes idle — on a starved host the simulated plant falls behind the waypoint stream, the cap fires, and the robot is reported complete while stranded short of the target (waldo-commander's hand-eye CI observed home "complete" with J1 29 deg from standby). Settling now resets the tick counter whenever the position error shrinks, so the cap only fires after 20 ticks without progress — preserving the anti-hang escape for real hardware's steady-state residual — and an unconverged completion logs the residual instead of passing silently. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01EdLG1uiKKZJ6aAeiojeRwd --- parol6/server/segment_player.py | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/parol6/server/segment_player.py b/parol6/server/segment_player.py index 3eb9ef5..080e2a2 100644 --- a/parol6/server/segment_player.py +++ b/parol6/server/segment_player.py @@ -17,7 +17,6 @@ from typing import TYPE_CHECKING import numpy as np -from pinokin import arrays_equal_n from parol6.commands._collision_guard import guard_joint_path from parol6.commands.base import CommandBase, ExecutionStatusCode @@ -60,6 +59,7 @@ class SegmentPlayer: "_inline_activated", "_settling", "_settle_ticks", + "_settle_err", "_last_shapes_version", ) @@ -72,6 +72,7 @@ def __init__(self, planner: MotionPlanner) -> None: self._inline_activated: bool = False self._settling: bool = False self._settle_ticks: int = 0 + self._settle_err: int = -1 self._last_shapes_version: int = 0 @property @@ -127,16 +128,36 @@ def tick(self, state: ControllerState) -> bool: self._step += 1 self._settling = False return True - # All waypoints sent — hold MOVE at target until Position_in converges + # All waypoints sent — hold MOVE at target until Position_in + # converges. The tick cap gates on stall, not elapsed time: + # while the firmware is still closing on the target (e.g. it + # fell behind the waypoint stream under CPU starvation) the + # segment stays active, so completion is never reported with + # the robot still in motion. target = active.trajectory_steps[-1] if not self._settling: self._settling = True self._settle_ticks = 0 + self._settle_err = -1 + err = 0 + for i in range(6): + d = int(state.Position_in[i]) - int(target[i]) + if d < 0: + d = -d + if d > err: + err = d + if self._settle_err < 0 or err < self._settle_err: + self._settle_err = err + self._settle_ticks = 0 self._settle_ticks += 1 - if ( - arrays_equal_n(state.Position_in[:6], target[:6]) - or self._settle_ticks > SETTLE_MAX_TICKS - ): + if err == 0 or self._settle_ticks > SETTLE_MAX_TICKS: + if err != 0: + logger.warning( + "Segment completed %d steps short of target " + "(no settle progress for %d ticks)", + err, + SETTLE_MAX_TICKS, + ) self._settling = False self._complete_segment(active, state) continue From dbeadbf0aba61c3552d5ca0b0850db445ff47a00 Mon Sep 17 00:00:00 2001 From: jepson2k <55201008+Jepson2k@users.noreply.github.com> Date: Thu, 6 Aug 2026 14:52:37 -0400 Subject: [PATCH 3/6] Annotate the clamped-spline bc assignment so ty keeps it Any scipy-stubs 1.18 types bc_type derivative values as array-likes only, while scipy requires scalars for 1-D y. ty narrows a bare-declared Any on assignment, so the tuple branch failed overload resolution; an annotated assignment keeps bc at its declared Any. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LkVPPMQg33tFuGCVLkyrJQ --- parol6/motion/geometry.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/parol6/motion/geometry.py b/parol6/motion/geometry.py index e36cb0b..b581a79 100644 --- a/parol6/motion/geometry.py +++ b/parol6/motion/geometry.py @@ -192,9 +192,10 @@ def generate_spline( pos_splines = [] for i in range(3): - bc: Any + # Annotated assignment keeps bc as Any: scipy-stubs' bc_type rejects + # the scalar derivative values scipy requires for 1-D y if velocity_start is not None and velocity_end is not None: - bc = ((1, float(velocity_start[i])), (1, float(velocity_end[i]))) + bc: Any = ((1, float(velocity_start[i])), (1, float(velocity_end[i]))) else: bc = "not-a-knot" spline = CubicSpline(timestamps_arr, waypoints_arr[:, i], bc_type=bc) From 804b898775b6142d18087c7a45f475c5bdb7046e Mon Sep 17 00:00:00 2001 From: jepson2k <55201008+Jepson2k@users.noreply.github.com> Date: Sat, 8 Aug 2026 03:30:21 -0400 Subject: [PATCH 4/6] Drop a stale ty ignore code and a redundant memoryview cast ty 0.0.69 resolves ndarray.tolist() without the ty-specific suppression and infers the frame memoryview union directly; it now warns on both leftovers, and warnings fail the lint hook. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LkVPPMQg33tFuGCVLkyrJQ --- parol6/protocol/wire.py | 2 +- parol6/server/transports/serial_transport.py | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/parol6/protocol/wire.py b/parol6/protocol/wire.py index 231d7e0..204f90f 100644 --- a/parol6/protocol/wire.py +++ b/parol6/protocol/wire.py @@ -43,7 +43,7 @@ def _enc_hook(obj: object) -> object: """Custom encoder hook for numpy types.""" if isinstance(obj, np.ndarray): - return obj.tolist() # type: ignore[no-matching-overload, ty:no-matching-overload] + return obj.tolist() # type: ignore[no-matching-overload] if isinstance(obj, (np.integer, np.floating)): return obj.item() raise NotImplementedError(f"Cannot encode {type(obj)}") diff --git a/parol6/server/transports/serial_transport.py b/parol6/server/transports/serial_transport.py index ace4b03..a26e822 100644 --- a/parol6/server/transports/serial_transport.py +++ b/parol6/server/transports/serial_transport.py @@ -8,7 +8,6 @@ import logging import os import time -from typing import cast import numba import numpy as np @@ -415,9 +414,7 @@ def get_latest_frame_view(self) -> tuple[memoryview | None, int, float]: Return a tuple of (memoryview|None, version:int, timestamp:float). The memoryview points to a stable 52-byte buffer which is updated by the reader. """ - mv = cast( - "memoryview | None", self._frame_mv if self._frame_version > 0 else None - ) + mv = self._frame_mv if self._frame_version > 0 else None return (mv, self._frame_version, self._frame_ts) def _update_hz_tracking(self) -> None: From adcd5b88144ddde5583b0460d51e34572aafb7fb Mon Sep 17 00:00:00 2001 From: jepson2k <55201008+Jepson2k@users.noreply.github.com> Date: Sat, 8 Aug 2026 12:47:53 -0400 Subject: [PATCH 5/6] Pin scipy-stubs in the dev extra Unpinned stub releases have broken the ty lint hook twice; ty itself stays unpinned deliberately while it is still in beta. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LkVPPMQg33tFuGCVLkyrJQ --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4a08877..b3e14fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,7 @@ dev = [ "trimesh", "fast-simplification", "rtree", - "scipy-stubs", + "scipy-stubs==1.18.0.1", "types-pyserial", ] From bd65722a418afe85b9db369fbb1257d9819c735a Mon Sep 17 00:00:00 2001 From: jepson2k <55201008+Jepson2k@users.noreply.github.com> Date: Sat, 8 Aug 2026 12:50:04 -0400 Subject: [PATCH 6/6] Split the scipy-stubs pin by Python version scipy-stubs 1.18.x requires Python >=3.12, so a single exact pin is uninstallable on the 3.11 CI jobs; 3.11 pins the last 1.17 release. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LkVPPMQg33tFuGCVLkyrJQ --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b3e14fa..78015cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,8 @@ dev = [ "trimesh", "fast-simplification", "rtree", - "scipy-stubs==1.18.0.1", + "scipy-stubs==1.17.1.5; python_version < '3.12'", + "scipy-stubs==1.18.0.1; python_version >= '3.12'", "types-pyserial", ]