diff --git a/examples/cartesian_space/dual_arm_clik.py b/examples/cartesian_space/dual_arm_clik.py
index b9cfc1f0c5025f2f0f0f2b8c9fece43a8dce83b5..a517a6f2a1b0880ae020b6a6822ab549b6482713 100644
--- a/examples/cartesian_space/dual_arm_clik.py
+++ b/examples/cartesian_space/dual_arm_clik.py
@@ -28,17 +28,17 @@ if __name__ == "__main__":
     args = get_args()
     robot = getRobotFromArgs(args)
     if args.randomly_generate_goal:
-        T_w_goal = getRandomlyGeneratedGoal(args)
+        T_w_absgoal = getRandomlyGeneratedGoal(args)
         if args.visualizer:
-            robot.visualizer_manager.sendCommand({"Mgoal": T_w_goal})
+            robot.visualizer_manager.sendCommand({"Mgoal": T_w_absgoal})
     else:
-        T_w_goal = defineGoalPointCLI(robot)
-        T_w_goal.rotation = np.eye(3)
-    T_absgoal_lgoal = pin.SE3.Identity()
-    T_absgoal_lgoal.translation[1] = 0.1
-    T_absgoal_rgoal = pin.SE3.Identity()
-    T_absgoal_rgoal.translation[1] = -0.1
-    moveLDualArm(args, robot, T_w_goal, T_absgoal_lgoal, T_absgoal_rgoal)
+        T_w_absgoal = defineGoalPointCLI(robot)
+        T_w_absgoal.rotation = np.eye(3)
+    T_absgoal_l = pin.SE3.Identity()
+    T_absgoal_l.translation[1] = 0.1
+    T_absgoal_r = pin.SE3.Identity()
+    T_absgoal_r.translation[1] = -0.1
+    moveLDualArm(args, robot, T_w_absgoal, T_absgoal_l, T_absgoal_r)
 
     if args.real:
         robot.stopRobot()
diff --git a/examples/cartesian_space/yumi_self_collision.py b/examples/cartesian_space/yumi_self_collision.py
index f0c681ad0d121c605870dae1b0a188d513ea899a..721330932a9cb83b741818fbde0b0d3ba1cdd88f 100644
--- a/examples/cartesian_space/yumi_self_collision.py
+++ b/examples/cartesian_space/yumi_self_collision.py
@@ -1,216 +1,61 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#
-# SPDX-License-Identifier: Apache-2.0
-# Copyright 2022 Stéphane Caron
-
-"""Yumi two-armed manipulator with definition of custom frame and
-collision avoidance w.r.t. those frames."""
-
-import meshcat_shapes
-import numpy as np
+from smc import getMinimalArgParser, getRobotFromArgs
+from smc.control.cartesian_space import getClikArgs
+from smc.robots.interfaces.dual_arm_interface import DualArmInterface
+from smc.control.joint_space.joint_space_point_to_point import moveJP
+from smc.util.define_random_goal import getRandomlyGeneratedGoal
+from smc.robots.utils import defineGoalPointCLI
+from smc.control.cartesian_space.pink_p2p import (
+    DualArmIKSelfAvoidanceViaEndEffectorSpheres,
+)
+
+
+import argparse
 import pinocchio as pin
-import qpsolvers
-from loop_rate_limiters import RateLimiter
-
-import pink
-from pink import solve_ik
-from pink.barriers import BodySphericalBarrier
-from pink.tasks import FrameTask, PostureTask
-from pink.visualization import start_meshcat_visualizer
 
-try:
-    from robot_descriptions.loaders.pinocchio import load_robot_description
-except ModuleNotFoundError as exc:
-    raise ModuleNotFoundError(
-        "Examples need robot_descriptions, "
-        "try `[conda|pip] install robot_descriptions`"
-    ) from exc  # noqa: E501
 
-
-if __name__ == "__main__":
-    # Load the robot and define the custom frames
-    robot = load_robot_description("yumi_description", root_joint=None)
-
-    # Left arm frame
-    l_frame_placement = pin.SE3()
-    l_frame_placement.translation = np.array([0.0, 0.0, 0.05])
-    l_frame_placement.rotation = np.eye(3)
-
-    l_frame = pin.Frame(
-        "yumi_barrier_l",
-        robot.model.getJointId("yumi_joint_6_l"),
-        robot.model.getFrameId("yumi_link_7_l"),
-        l_frame_placement,
-        pin.FrameType.OP_FRAME,
+def get_args() -> argparse.Namespace:
+    parser = getMinimalArgParser()
+    parser.description = "Run closed loop inverse kinematics \
+    of various kinds. Make sure you know what the goal is before you run!"
+    parser = getClikArgs(parser)
+    parser.add_argument(
+        "--randomly-generate-goal",
+        action=argparse.BooleanOptionalAction,
+        default=False,
+        help="if true, the target pose is randomly generated, if false you type it target translation in via text input",
     )
+    args = parser.parse_args()
+    return args
 
-    robot.model.addFrame(l_frame)
-
-    # Right arm frame
-    r_frame_placement = pin.SE3()
-    r_frame_placement.translation = np.array([0.0, 0.0, 0.05])
-    r_frame_placement.rotation = np.eye(3)
-
-    r_frame = pin.Frame(
-        "yumi_barrier_r",
-        robot.model.getJointId("yumi_joint_6_r"),
-        robot.model.getFrameId("yumi_link_7_r"),
-        r_frame_placement,
-        pin.FrameType.OP_FRAME,
-    )
-
-    robot.model.addFrame(r_frame)
-    # Redefining robot data to take into account the new frames
-    robot.data = pin.Data(robot.model)
-
-    viz = start_meshcat_visualizer(robot)
-
-    # Pink tasks
-    left_end_effector_task = FrameTask(
-        "yumi_link_7_l",
-        position_cost=50.0,  # [cost] / [m]
-        orientation_cost=10.0,  # [cost] / [rad]
-    )
-    right_end_effector_task = FrameTask(
-        "yumi_link_7_r",
-        position_cost=50.0,  # [cost] / [m]
-        orientation_cost=10.0,  # [cost] / [rad]
-    )
-
-    # Pink barriers
-    ee_barrier = BodySphericalBarrier(
-        ("yumi_barrier_l", "yumi_barrier_r"),
-        d_min=0.25,
-        gain=100.0,
-        safe_displacement_gain=1.0,
-    )
 
-    posture_task = PostureTask(
-        cost=1e-3,  # [cost] / [rad]
-    )
-
-    cbf_list = [ee_barrier]
-    tasks = [left_end_effector_task, right_end_effector_task, posture_task]
-
-    q_ref = np.array(
-        [
-            0.045,
-            -0.155,
-            -0.394,
-            -0.617,
-            -0.939,
-            -0.343,
-            -1.216,
-            0,
-            0,
-            -0.374,
-            -0.249,
-            0.562,
-            -0.520,
-            0.934,
-            -0.337,
-            1.400,
-            0,
-            0,
-        ]
-    )
-    configuration = pink.Configuration(robot.model, robot.data, q_ref)
-    for task in tasks:
-        task.set_target_from_configuration(configuration)
-    viz.display(configuration.q)
-
-    viewer = viz.viewer
-    meshcat_shapes.frame(viewer["left_end_effector"], opacity=1.0)
-    meshcat_shapes.sphere(
-        viewer["left_ee_barrier"],
-        opacity=0.4,
-        color=0xFF0000,
-        radius=0.125,
-    )
-    meshcat_shapes.sphere(
-        viewer["right_ee_barrier"],
-        opacity=0.4,
-        color=0x00FF00,
-        radius=0.125,
-    )
-    meshcat_shapes.frame(viewer["right_end_effector"], opacity=1.0)
-    meshcat_shapes.frame(viewer["left_end_effector_target"], opacity=1.0)
-    meshcat_shapes.frame(viewer["right_end_effector_target"], opacity=1.0)
-
-    # Select QP solver
-    solver = qpsolvers.available_solvers[0]
-    if "osqp" in qpsolvers.available_solvers:
-        solver = "osqp"
-
-    rate = RateLimiter(frequency=200.0)
-    dt = rate.period
-    t = 0.0  # [s]
-    l_y_des = np.array([0.392, 0.392, 0.6])
-    r_y_des = np.array([0.392, 0.392, 0.6])
-    l_dy_des = np.zeros(3)
-    r_dy_des = np.zeros(3)
-
-    while True:
-        # Calculate desired trajectory
-        A = 0.1
-        B = 0.1
-        # z -- 0.4 - 0.8
-        l_y_des[:] = (
-            0.6,
-            0.1 + B * np.sin(t),
-            0.6 + A * np.sin(t),
-        )
-        r_y_des[:] = (
-            0.6,
-            -0.1 - B * np.sin(t),
-            0.6 + A * np.sin(t),
-        )
-        l_dy_des[:] = 0, B * np.cos(t), A * np.cos(t)
-        r_dy_des[:] = 0, -B * np.cos(t), A * np.cos(t)
-
-        left_end_effector_task.transform_target_to_world.translation = l_y_des
-        right_end_effector_task.transform_target_to_world.translation = r_y_des
-
-        # Update visualization frames
-        viewer["left_end_effector"].set_transform(
-            configuration.get_transform_frame_to_world(left_end_effector_task.frame).np
-        )
-        viewer["right_end_effector"].set_transform(
-            configuration.get_transform_frame_to_world(right_end_effector_task.frame).np
-        )
-        viewer["left_end_effector_target"].set_transform(
-            left_end_effector_task.transform_target_to_world.np
-        )
-        viewer["right_end_effector_target"].set_transform(
-            right_end_effector_task.transform_target_to_world.np
-        )
-
-        lb = configuration.get_transform_frame_to_world("yumi_barrier_l")
-        rb = configuration.get_transform_frame_to_world("yumi_barrier_r")
-
-        viewer["left_ee_barrier"].set_transform(lb.np)
-        viewer["right_ee_barrier"].set_transform(rb.np)
-
-        velocity = solve_ik(
-            configuration,
-            tasks,
-            dt,
-            solver=solver,
-            barriers=cbf_list,
-        )
-        configuration.integrate_inplace(velocity, dt)
-        dist_ee = (
-            np.linalg.norm(
-                configuration.get_transform_frame_to_world("yumi_barrier_l").translation
-                - configuration.get_transform_frame_to_world(
-                    "yumi_barrier_r"
-                ).translation
-            )
-            * 100
-        )
-        print(f"Distance between end effectors: {dist_ee :0.1f}cm")
-        # Visualize result at fixed FPS
-        viz.display(configuration.q)
-        rate.sleep()
-        t += dt
+if __name__ == "__main__":
+    args = get_args()
+    robot = getRobotFromArgs(args)
+    assert issubclass(robot.__class__, DualArmInterface)
+    # go to home position
+    moveJP(robot._comfy_configuration, args, robot)
+
+    if args.randomly_generate_goal:
+        T_w_absgoal = getRandomlyGeneratedGoal(args)
+        if args.visualizer:
+            robot.visualizer_manager.sendCommand({"Mgoal": T_w_absgoal})
+    else:
+        T_w_absgoal = defineGoalPointCLI(robot)
+        T_w_absgoal.rotation = robot.T_w_abs.rotation.copy()
+    T_absgoal_l = pin.SE3.Identity()
+    T_absgoal_l.translation[1] = 0.15
+    T_absgoal_r = pin.SE3.Identity()
+    T_absgoal_r.translation[1] = -0.15
+
+    DualArmIKSelfAvoidanceViaEndEffectorSpheres(
+        T_w_absgoal, T_absgoal_l, T_absgoal_r, args, robot
+    )
+
+    if args.real:
+        robot.stopRobot()
+
+    if args.visualizer:
+        robot.killManipulatorVisualizer()
+
+    if args.save_log:
+        robot._log_manager.saveLog()
diff --git a/python/smc/control/cartesian_space/pink_p2p.py b/python/smc/control/cartesian_space/pink_p2p.py
new file mode 100644
index 0000000000000000000000000000000000000000..87526d5c9dc1b0683757fea1236916f1943d8c00
--- /dev/null
+++ b/python/smc/control/cartesian_space/pink_p2p.py
@@ -0,0 +1,151 @@
+from smc.control.control_loop_manager import ControlLoopManager
+from smc.control.controller_templates.point_to_point import DualEEP2PCtrlLoopTemplate
+from smc.robots.interfaces.dual_arm_interface import DualArmInterface
+
+import pink
+from pink.barriers import BodySphericalBarrier
+from pink.tasks import FrameTask, PostureTask
+
+import numpy as np
+import qpsolvers
+import argparse
+from functools import partial
+from collections import deque
+import pinocchio as pin
+
+
+# TODO: butcher pink to avoid redundancies with smc like configuration.
+# right now there is no time and we're just shoving it in.
+def DualArmIKSelfAvoidanceViaEndEffectorSpheresCtrlLoop(
+    tasks: list[pink.FrameTask],
+    cbf_list: list[pink.barriers.Barrier],
+    solver: str,
+    T_w_absgoal: pin.SE3,
+    T_absgoal_l: pin.SE3,
+    T_absgoal_r: pin.SE3,
+    args: argparse.Namespace,
+    robot: DualArmInterface,
+    t: int,
+    past_data: dict[str, deque[np.ndarray]],
+) -> tuple[np.ndarray, dict[str, np.ndarray], dict[str, np.ndarray]]:
+    configuration = pink.Configuration(robot.model, robot.data, robot.q)
+    # NOTE: there are limits in pink, but they are a class where G matrix is constucted
+    # combining lb and ub and forming Gx \leq h
+    # this makes it possible to combine other stuff into this inequality constraint
+    configuration.model.velocityLimit = robot._max_v
+
+    T_w_lgoal = T_absgoal_l.act(T_w_absgoal)
+    T_w_rgoal = T_absgoal_r.act(T_w_absgoal)
+    # next_goal_l = pin.SE3.Interpolate(robot.T_w_l, T_w_lgoal, 0.001)
+    # next_goal_r = pin.SE3.Interpolate(robot.T_w_r, T_w_rgoal, 0.001)
+    # tasks[0].set_target(next_goal_l)
+    # tasks[1].set_target(next_goal_r)
+    tasks[0].set_target(T_w_lgoal)
+    tasks[1].set_target(T_w_rgoal)
+
+    v_cmd = pink.solve_ik(
+        configuration,
+        tasks,
+        robot.dt,
+        solver=solver,
+        barriers=cbf_list,
+        # safety_break=True,
+        safety_break=False,
+    )
+    dist_ee = np.linalg.norm(robot.T_w_l.translation - robot.T_w_r.translation)
+    log_item = {"dist_ees": dist_ee.reshape((1,))}
+    return v_cmd, {}, log_item
+
+
+def DualArmIKSelfAvoidanceViaEndEffectorSpheres(
+    T_w_absgoal: pin.SE3,
+    T_absgoal_l: pin.SE3,
+    T_absgoal_r: pin.SE3,
+    args: argparse.Namespace,
+    robot: DualArmInterface,
+    run: bool = True,
+) -> ControlLoopManager | None:
+
+    # Pink tasks
+    left_end_effector_task = FrameTask(
+        "robl_tool0",
+        position_cost=50.0,  # [cost] / [m]
+        orientation_cost=10.0,  # [cost] / [rad]
+    )
+    right_end_effector_task = FrameTask(
+        "robr_tool0",
+        position_cost=50.0,  # [cost] / [m]
+        orientation_cost=10.0,  # [cost] / [rad]
+    )
+
+    # Pink barriers
+    ee_barrier = BodySphericalBarrier(
+        ("robl_tool0", "robr_tool0"),
+        d_min=0.15,
+        gain=100.0,
+        safe_displacement_gain=1.0,
+    )
+
+    posture_task = PostureTask(
+        cost=1e-3,  # [cost] / [rad]
+    )
+
+    cbf_list = [ee_barrier]
+    tasks = [left_end_effector_task, right_end_effector_task, posture_task]
+
+    # NOTE: model and data are shared pointers between configuration and robot.
+    # this is redundant as hell, but I don't have the time butcher pink right now
+    configuration = pink.Configuration(robot.model, robot.data, robot.q)
+    posture_task.set_target_from_configuration(configuration)
+    left_end_effector_task.set_target(T_absgoal_l.act(T_w_absgoal))
+    right_end_effector_task.set_target(T_absgoal_r.act(T_w_absgoal))
+
+    #    meshcat_shapes.sphere(
+    #        viewer["left_ee_barrier"],
+    #        opacity=0.4,
+    #        color=0xFF0000,
+    #        radius=0.125,
+    #    )
+    #    meshcat_shapes.sphere(
+    #        viewer["right_ee_barrier"],
+    #        opacity=0.4,
+    #        color=0x00FF00,
+    #        radius=0.125,
+    #    )
+    #    meshcat_shapes.frame(viewer["left_end_effector_target"], opacity=1.0)
+    #    meshcat_shapes.frame(viewer["right_end_effector_target"], opacity=1.0)
+
+    # TODO: allow proxsuite solvers also (primarily because they can be saved and reused with warmstarting)
+    # Select QP solver
+    solver = qpsolvers.available_solvers[0]
+    if "osqp" in qpsolvers.available_solvers:
+        solver = "osqp"
+
+    ctrl_loop = partial(
+        DualArmIKSelfAvoidanceViaEndEffectorSpheresCtrlLoop,
+        tasks,
+        cbf_list,
+    )
+    control_loop = partial(
+        DualEEP2PCtrlLoopTemplate,
+        solver,
+        T_w_absgoal,
+        T_absgoal_l,
+        T_absgoal_r,
+        ctrl_loop,
+        args,
+        robot,
+    )
+    log_item = {
+        "qs": robot.q,
+        "dqs": np.zeros(robot.nv),
+        "dqs_cmd": np.zeros((robot.model.nv,)),
+        "l_err_norm": np.zeros((1,)),
+        "r_err_norm": np.zeros((1,)),
+        "dist_ees": np.zeros((1,)),
+    }
+    loop_manager = ControlLoopManager(robot, control_loop, args, {}, log_item)
+    if not run:
+        return loop_manager
+    else:
+        loop_manager.run()
diff --git a/python/smc/control/control_loop_manager.py b/python/smc/control/control_loop_manager.py
index 0e3175d715f0477842134342e9566b9eee327b0d..d7bfefb8a9b59f0337eadddd1400cf989e06cf6d 100644
--- a/python/smc/control/control_loop_manager.py
+++ b/python/smc/control/control_loop_manager.py
@@ -1,5 +1,6 @@
 from argparse import Namespace
 from smc.robots.abstract_robotmanager import AbstractRobotManager
+from smc.robots.interfaces.dual_arm_interface import DualArmInterface
 from smc.robots.interfaces.single_arm_interface import SingleArmInterface
 from smc.robots.interfaces.mobile_base_interface import MobileBaseInterface
 from smc.multiprocessing.process_manager import ProcessManager
@@ -151,12 +152,21 @@ class ControlLoopManager:
                         "q": self.robot_manager._q,
                     }
                 )
+                # NOTE: for dual armed robots it displays T_w_abs
                 if issubclass(self.robot_manager.__class__, SingleArmInterface):
                     self.robot_manager.visualizer_manager.sendCommand(
                         {
                             "T_w_e": self.robot_manager.T_w_e,
                         }
                     )
+                # TODO: implement in visualizer
+                #                if issubclass(self.robot_manager.__class__, DualArmInterface):
+                #                    self.robot_manager.visualizer_manager.sendCommand(
+                #                        {
+                #                            "T_w_l": self.robot_manager.T_w_l,
+                #                            "T_w_r": self.robot_manager.T_w_r,
+                #                        }
+                #                    )
                 if issubclass(self.robot_manager.__class__, MobileBaseInterface):
                     self.robot_manager.visualizer_manager.sendCommand(
                         {"T_base": self.robot_manager.T_w_b}
@@ -164,7 +174,7 @@ class ControlLoopManager:
 
                 if self.args.visualize_collision_approximation:
                     raise NotImplementedError
-                # TODO: here call robot manager's update ellipses function
+                # TODO: here call robot manager's update ellipses function or whatever approximation is used
 
             if self.args.plotter:
                 # don't put new stuff in if it didn't handle the previous stuff.
diff --git a/python/smc/control/joint_space/joint_space_point_to_point.py b/python/smc/control/joint_space/joint_space_point_to_point.py
index d506184c74e9d5992e2f4402d575b8edcf1ccb1d..93e30cfe0f0a02b246b76eb17de0a9459f71da2f 100644
--- a/python/smc/control/joint_space/joint_space_point_to_point.py
+++ b/python/smc/control/joint_space/joint_space_point_to_point.py
@@ -5,6 +5,7 @@ import numpy as np
 from functools import partial
 from collections import deque
 from argparse import Namespace
+import pinocchio as pin
 
 
 def moveJControlLoop(
@@ -21,13 +22,20 @@ def moveJControlLoop(
     """
     breakFlag = False
     q = robot.q
-    q_error = q_desired - q
-    if np.linalg.norm(q_error) < 1e-3:
+    q_error = pin.difference(robot.model, q, q_desired)
+    err_norm = np.linalg.norm(q_error)
+    if err_norm < 1e-3:
         breakFlag = True
-    K = 120
+    K = 320
     qd = K * q_error * robot.dt
     robot.sendVelocityCommand(qd)
-    return breakFlag, {}, {}
+    log_item = {
+        "qs": robot.q,
+        "dqs": robot.v,
+        "dqs_cmd": qd,
+        "err_norm": err_norm.reshape((1,)),
+    }
+    return breakFlag, {}, log_item
 
 
 # TODO:
@@ -44,10 +52,14 @@ def moveJP(q_desired: np.ndarray, args: Namespace, robot: AbstractRobotManager)
     assert type(q_desired) == np.ndarray
     controlLoop = partial(moveJControlLoop, q_desired, args, robot)
     # we're not using any past data or logging, hence the empty arguments
-    loop_manager = ControlLoopManager(robot, controlLoop, args, {}, {})
+    log_item = {
+        "qs": np.zeros(robot.nq),
+        "dqs": np.zeros(robot.nv),
+        "dqs_cmd": np.zeros(robot.nv),
+        "err_norm": np.zeros((1,)),
+    }
+    loop_manager = ControlLoopManager(robot, controlLoop, args, {}, log_item)
     loop_manager.run()
-    # TODO: remove, this isn't doing anything
-    # time.sleep(0.01)
     if args.debug_prints:
         print("MoveJP done: convergence achieved, reached destionation!")
 
@@ -122,8 +134,8 @@ def moveJPIControlLoop(
 
     log_item["qs"] = q
     log_item["dqs"] = robot.v
-    log_item["integral_error"] = integral_error  # Save updated integral error
-    log_item["e_prev"] = q_error  # Save current position error
+    log_item["integral_error"] = integral_error.flatten()  # Save updated integral error
+    log_item["e_prev"] = q_error.flatten()  # Save current position error
     return breakFlag, save_past_dict, log_item
 
 
diff --git a/python/smc/robots/abstract_robotmanager.py b/python/smc/robots/abstract_robotmanager.py
index 6605d92b26b30d35d11275bd55a6a3c8df16cb40..5a298331264f5652bc783c2b08040321095664ec 100644
--- a/python/smc/robots/abstract_robotmanager.py
+++ b/python/smc/robots/abstract_robotmanager.py
@@ -58,9 +58,11 @@ class AbstractRobotManager(abc.ABC):
         self._max_v = np.clip(self._MAX_V, 0.0, args.max_v_percentage * self._MAX_V)
         # NOTE: make sure you've read pinocchio docs and understand why
         #       nq and nv are not necessarily the same number
-        self._q = np.zeros(self.model.nq)
-        self._v = np.zeros(self.model.nv)
-        self._a = np.zeros(self.model.nv)
+        self._q = np.zeros(self.nq)
+        self._v = np.zeros(self.nv)
+        self._a = np.zeros(self.nv)
+
+        self._comfy_configuration: np.ndarray
 
         # TODO: each robot should know which grippers are available
         # and set this there.
diff --git a/python/smc/robots/implementations/yumi.py b/python/smc/robots/implementations/yumi.py
index 82194c49d6270391893c31d1e5a50346410c3d73..2bd17e842329458bce6ef828e29f51f078db98a4 100644
--- a/python/smc/robots/implementations/yumi.py
+++ b/python/smc/robots/implementations/yumi.py
@@ -6,6 +6,7 @@ from argparse import Namespace
 from importlib.resources import files
 from os import path
 import pinocchio as pin
+import numpy as np
 
 
 class AbstractYuMiRobotManager(DualArmInterface):
@@ -19,6 +20,42 @@ class AbstractYuMiRobotManager(DualArmInterface):
         self._r_ee_frame_id = self.model.getFrameId("robr_tool0")
         self._MAX_ACCELERATION = 1.7  # const
         self._MAX_QD = 3.14  # const
+        #        self._comfy_configuration = np.array(
+        #            [
+        #                -0.7019,
+        #                0.03946,
+        #                1.13817,
+        #                0.40438,
+        #                1.59454,
+        #                0.37243,
+        #                -1.3882,
+        #                1.17810,
+        #                -0.00055,
+        #                -1.7492,
+        #                0.41061,
+        #                -2.0604,
+        #                0.30449,
+        #                1.72462,
+        #            ]
+        #        )
+        self._comfy_configuration = np.array(
+            [
+                0.045,
+                -0.155,
+                -0.394,
+                -0.617,
+                -0.939,
+                -0.343,
+                -1.216,
+                -0.374,
+                -0.249,
+                0.562,
+                -0.520,
+                0.934,
+                -0.337,
+                1.400,
+            ]
+        )
 
         self._mode: DualArmInterface.control_mode = (
             DualArmInterface.control_mode.both_arms
diff --git a/python/smc/visualization/visualizer.py b/python/smc/visualization/visualizer.py
index 2851764d1b7b6fd55bfa0fb0e5150afae7caf8db..041dcbae7d17949db4997530243675b0ba5018e1 100644
--- a/python/smc/visualization/visualizer.py
+++ b/python/smc/visualization/visualizer.py
@@ -2,6 +2,10 @@ import numpy as np
 from smc.visualization.meshcat_viewer_wrapper.visualizer import MeshcatVisualizer
 from pinocchio.visualize import MeshcatVisualizer as UnWrappedMeshcat
 import meshcat_shapes
+import pinocchio as pin
+from multiprocessing import Queue
+from argparse import Namespace
+from typing import Any
 
 # tkinter stuff for later reference
 # from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
@@ -9,16 +13,26 @@ import meshcat_shapes
 # from tkinter.ttk import *
 
 
-def manipulatorVisualizer(model, collision_model, visual_model, args, cmd, queue):
+def manipulatorVisualizer(
+    model: pin.Model,
+    collision_model: pin.GeometryModel,
+    visual_model: pin.GeometryModel,
+    args: Namespace,
+    cmd: dict[str, Any],
+    queue: Queue,
+) -> None:
     viz = MeshcatVisualizer(
         model=model, collision_model=collision_model, visual_model=visual_model
     )
     viz.loadViewerModel()
     # display the initial pose
     viz.display(cmd["q"])
-    # set shapes we know we'll use
+    # TODO: load shapes depending on robot type
+    # set shapes we might use
     meshcat_shapes.frame(viz.viewer["Mgoal"], opacity=0.5)
     meshcat_shapes.frame(viz.viewer["T_w_e"], opacity=0.5)
+    meshcat_shapes.frame(viz.viewer["T_w_l"], opacity=0.5)
+    meshcat_shapes.frame(viz.viewer["T_w_r"], opacity=0.5)
     meshcat_shapes.frame(viz.viewer["T_base"], opacity=0.5)
     print("MANIPULATORVISUALIZER: FULLY ONLINE")
     try:
@@ -37,6 +51,10 @@ def manipulatorVisualizer(model, collision_model, visual_model, args, cmd, queue
                     viz.viewer["Mgoal"].set_transform(cmd["Mgoal"].homogeneous)
                 if key == "T_w_e":
                     viz.viewer["T_w_e"].set_transform(cmd["T_w_e"].homogeneous)
+                if key == "T_w_l":
+                    viz.viewer["T_w_l"].set_transform(cmd["T_w_l"].homogeneous)
+                if key == "T_w_r":
+                    viz.viewer["T_w_r"].set_transform(cmd["T_w_r"].homogeneous)
                 if key == "T_base":
                     viz.viewer["T_base"].set_transform(cmd["T_base"].homogeneous)
                 if key == "q":