diff --git a/examples/cart_pulling/control_sub_problems/dual_arm_cart_pulling.py b/examples/cart_pulling/control_sub_problems/dual_arm_cart_pulling.py
index a728f36b3f9a4df81d1ab1b584895df0ca3104bb..0876a250f66824132c4e5fab1903325e3c285f2d 100644
--- a/examples/cart_pulling/control_sub_problems/dual_arm_cart_pulling.py
+++ b/examples/cart_pulling/control_sub_problems/dual_arm_cart_pulling.py
@@ -1,17 +1,20 @@
-from smc.robots.implementations.mobile_yumi import SimulatedMobileYuMiRobotManager
-from smc import getMinimalArgParser
+from smc.robots.abstract_robotmanager import AbstractRobotManager
+from smc.robots.utils import getRobotFromArgs
 from smc.path_generation.maps.premade_maps import createSampleStaticMap
 from smc.path_generation.path_math.path2d_to_6d import path2D_to_SE3
-from smc.control.optimal_control.util import get_OCP_args
-from smc.control.cartesian_space import getClikArgs
-from smc.path_generation.planner import starPlanner, getPlanningArgs
-from smc.control.optimal_control.croco_mpc_point_to_point import (
-    CrocoDualEEAndBaseP2PMPC,
+from smc.path_generation.planner import starPlanner
+from smc.multiprocessing import ProcessManager
+from smc.control.optimal_control.croco_point_to_point.mpc.base_reference_mpc import (
+    CrocoBaseP2PMPC,
 )
-from smc.control.optimal_control.croco_mpc_path_following import (
-    BaseAndDualEEPathFollowingMPC,
+from smc.control.joint_space.joint_space_point_to_point import moveJP
+from smc.control.cartesian_space.pink_p2p import (
+    DualArmIKSelfAvoidanceViaEndEffectorSpheres,
 )
-from smc.multiprocessing import ProcessManager
+
+from utils import get_args, constructInitialT_w_abs
+from dual_arm_cart_pulling_control_loop import DualArmCartPulling
+from fixed_path_planner import contructPath, fixedPathPlanner
 
 import time
 import numpy as np
@@ -19,86 +22,101 @@ from functools import partial
 import pinocchio as pin
 
 
-def get_args():
-    parser = getMinimalArgParser()
-    parser = get_OCP_args(parser)
-    parser = getClikArgs(parser)  # literally just for goal error
-    parser = getPlanningArgs(parser)
-    parser.add_argument(
-        "--handlebar-height",
-        type=float,
-        default=0.5,
-        help="heigh of handlebar of the cart to be pulled",
-    )
-    parser.add_argument(
-        "--base-to-handlebar-preferred-distance",
-        type=float,
-        default=0.5,
-        help="prefered path arclength from mobile base position to handlebar",
-    )
-    args = parser.parse_args()
-    return args
-
-
 if __name__ == "__main__":
     args = get_args()
     assert args.robot == "myumi"
-    robot = SimulatedMobileYuMiRobotManager(args)
-    # TODO: HOW IS IT POSSIBLE THAT T_W_E IS WRONG WITHOUT STEP CALLED HERE?????????????????
+    robot = getRobotFromArgs(args)
     T_absgoal_l = pin.SE3.Identity()
-    T_absgoal_l.translation[1] = 0.1
+    T_absgoal_l.translation[1] = 0.15
     T_absgoal_r = pin.SE3.Identity()
-    T_absgoal_r.translation[1] = -0.1
-    robot._step()
-    robot._q[0] = 9.0
-    robot._q[1] = 4.0
-    robot._step()
-    x0 = np.concatenate([robot.q, robot.v])
-    goal = np.array([0.5, 5.5])
-
-    T_w_abs = robot.T_w_abs
-    planning_function = partial(starPlanner, goal)
-    # here we're following T_w_e reference so that's what we send
-    path_planner = ProcessManager(
-        args, planning_function, T_w_abs.translation[:2], 3, None
-    )
-    _, map_as_list = createSampleStaticMap()
-    if args.visualizer:
-        robot.sendRectangular2DMapToVisualizer(map_as_list)
-        # time.sleep(5)
-
-    T_w_abs = robot.T_w_abs
-    data = None
-    # get first path
-    ##########################################3
-    #                initialize
-    ###########################################
-    while data is None:
-        path_planner.sendCommand(T_w_abs.translation[:2])
-        data = path_planner.getData()
-        # time.sleep(1 / args.ctrl_freq)
-        time.sleep(1)
-
-    _, path2D = data
-    path2D = np.array(path2D).reshape((-1, 2))
-    pathSE3 = path2D_to_SE3(path2D, args.handlebar_height)
+    T_absgoal_r.translation[1] = -0.15
+
+    # TODO: before running on the real robot change this to just reading the current position!!
+    if args.planner:
+        robot._step()
+        robot._q[0] = 9.0
+        robot._q[1] = 4.0
+        robot._step()
+        x0 = np.concatenate([robot.q, robot.v])
+        goal = np.array([0.5, 5.5])
+
+    ######################################################
+    # 1. initialize path planner, or instantiate pre-made path
+    #    and make that a path planner, for the base
+    #################################################
+    if args.planner:
+        planning_function = partial(starPlanner, goal)
+        # here we're following T_w_e reference so that's what we send
+        path_planner = ProcessManager(
+            args, planning_function, robot.T_w_b.translation[:2], 3, None
+        )
+        _, map_as_list = createSampleStaticMap()
+        # TODO: put in real lab map
+        if args.visualizer:
+            robot.sendRectangular2DMapToVisualizer(map_as_list)
+
+        data = None
+        while data is None:
+            path_planner.sendCommand(robot.T_w_b.translation[:2])
+            data = path_planner.getData()
+            time.sleep(1)
+
+        _, path_base = data
+        path_base = np.array(path_base).reshape((-1, 2))
+        path_base = np.hstack((path_base, np.zeros((len(path_base), 1))))
+    else:
+        path_base = contructPath(args, robot)
+        path_planner = partial(fixedPathPlanner, [0], path_base)
+
+    ################################################
+    # 2. construct initial T_w_abs (starting middle of handlebar pose)
+    ################################################
+    pathSE3 = path2D_to_SE3(path_base, args.handlebar_height)
+    T_w_absgoal = constructInitialT_w_abs(args, path_base, pathSE3[0].rotation)
+
     if args.visualizer:
-        # TODO: document this somewhere
         robot.visualizer_manager.sendCommand({"Mgoal": pathSE3[0]})
-    if np.linalg.norm(pin.log6(T_w_abs.actInv(pathSE3[0]))) > 1e-2:
-        print("going to initial path position")
-        p_base = pathSE3[0].translation.copy()
-        p_base[0] -= args.base_to_handlebar_preferred_distance
-        p_base[2] = 0.0
-        print(pathSE3[0].translation)
-        print(p_base)
-        CrocoDualEEAndBaseP2PMPC(
-            args, robot, pathSE3[0], T_absgoal_l, T_absgoal_r, p_base
-        )
-    print("initialized!")
-    BaseAndDualEEPathFollowingMPC(args, robot, path_planner, T_absgoal_l, T_absgoal_r)
 
-    print("final position:", robot.T_w_e)
+    ##################################################
+    # 3. get arms into comfy position
+    ####################################################
+    print("putting arms to a comfy configuration")
+    robot.mode = AbstractRobotManager.control_mode.upper_body
+    moveJP(robot._comfy_configuration, args, robot)
+
+    ###################################################
+    # 4. get base to starting pose
+    ###################################################
+    print("getting base to start of path")
+    p_basegoal = path_base[0]
+    robot.mode = AbstractRobotManager.control_mode.base_only
+    CrocoBaseP2PMPC(args, robot, p_basegoal)
+
+    # NOTE: alternative: navigate to start point and position arms simulatenously
+    # NOTE: does not work well :( (would need finessing which is too time-consuming for what it's worth)
+    # CrocoDualEEAndBaseP2PMPC(args, robot, pathSE3[0], T_absgoal_l, T_absgoal_r, p_base)
+
+    ###################################################
+    # DualArmMoveL 10 cm above handlebar
+    ###################################################
+    robot.mode = AbstractRobotManager.control_mode.upper_body
+    T_w_absgoal.translation[2] -= 0.1
+    DualArmIKSelfAvoidanceViaEndEffectorSpheres(
+        T_w_absgoal, T_absgoal_l, T_absgoal_r, args, robot
+    )
+    ###################################################
+    # DualArmMoveL down to handlebar
+    ###################################################
+    T_w_absgoal.translation[2] += 0.1
+    DualArmIKSelfAvoidanceViaEndEffectorSpheres(
+        T_w_absgoal, T_absgoal_l, T_absgoal_r, args, robot
+    )
+    ###################################################
+    # TODO: (5) grip handlebar with gripper (sleep before, or wait for keyboard input idk)
+    ###################################################
+    # time.sleep(5)
+
+    DualArmCartPulling(args, robot, path_planner, T_absgoal_l, T_absgoal_r)
 
     if args.real:
         robot.stopRobot()
diff --git a/examples/cart_pulling/control_sub_problems/dual_arm_cart_pulling_control_loop.py b/examples/cart_pulling/control_sub_problems/dual_arm_cart_pulling_control_loop.py
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..28f4a868f4ad722e54106b81aa1635e052077d12 100644
--- a/examples/cart_pulling/control_sub_problems/dual_arm_cart_pulling_control_loop.py
+++ b/examples/cart_pulling/control_sub_problems/dual_arm_cart_pulling_control_loop.py
@@ -0,0 +1,173 @@
+from smc.robots.interfaces.whole_body_interface import (
+    DualArmWholeBodyInterface,
+)
+from smc.control.control_loop_manager import ControlLoopManager
+from smc.multiprocessing.process_manager import ProcessManager
+from smc.control.optimal_control.abstract_croco_ocp import CrocoOCP
+from smc.control.optimal_control.croco_path_following.ocp.base_and_dual_arm_reference_ocp import (
+    BaseAndDualArmEEPathFollowingOCP,
+)
+from smc.path_generation.path_math.cart_pulling_path_math import (
+    construct_EE_path,
+)
+from smc.path_generation.path_math.path_to_trajectory import (
+    path2D_to_trajectory2D,
+)
+from smc.control.controller_templates.path_following_template import (
+    PathFollowingFromPlannerCtrllLoopTemplate,
+)
+
+import numpy as np
+from functools import partial
+import types
+from argparse import Namespace
+from pinocchio import SE3, log6
+from collections import deque
+
+
+def initializePastData(
+    args: Namespace, T_w_e: SE3, p_base: np.ndarray, max_base_v: float
+) -> np.ndarray:
+    # prepopulate past data to make base and cart be on the same path in the past
+    # (which didn't actually happen because this just started)
+    p_ee = T_w_e.translation[:2]
+    straight_line_path = np.linspace(p_ee, p_base, args.past_window_size)
+    # straight_line_path_timed = path2D_timed(args, straight_line_path, max_base_v)
+    # return straight_line_path_timed # this one is shortened to args.n_knots! and we want the whole buffer
+    return straight_line_path
+
+
+def DualArmCartPullingControlLoop(
+    T_absgoal_l: SE3,
+    T_absgoal_r: SE3,
+    ocp: CrocoOCP,
+    path2D_base: np.ndarray,
+    args: Namespace,
+    robot: DualArmWholeBodyInterface,
+    t: int,
+    past_data: dict[str, deque[np.ndarray]],
+) -> tuple[np.ndarray, dict[str, np.ndarray], dict[str, np.ndarray]]:
+    """
+    BaseAndDualEEPathFollowingMPCControlLoop
+    -----------------------------
+    cart pulling dual arm control loop
+    """
+    p = robot.T_w_b.translation[:2]
+
+    # NOTE: this one is obtained as the future path from path planner
+    max_base_v = np.linalg.norm(robot._max_v[:2])
+    trajectory_base = path2D_to_trajectory2D(args, path2D_base, max_base_v)
+    trajectory_base = np.hstack((trajectory_base, np.zeros((len(trajectory_base), 1))))
+
+    trajectorySE3_handlebar = construct_EE_path(args, p, past_data["path2D"])
+    trajectorySE3_l = []
+    trajectorySE3_r = []
+    for traj_pose in trajectorySE3_handlebar:
+        trajectorySE3_l.append(T_absgoal_l.act(traj_pose))
+        trajectorySE3_r.append(T_absgoal_r.act(traj_pose))
+
+    if args.visualizer:
+        if t % int(np.ceil(args.ctrl_freq / 25)) == 0:
+            robot.visualizer_manager.sendCommand({"path": trajectory_base})
+            robot.visualizer_manager.sendCommand(
+                {"frame_path": trajectorySE3_handlebar}
+            )
+
+    x0 = np.concatenate([robot.q, robot.v])
+    ocp.warmstartAndReSolve(
+        x0, data=(trajectory_base, trajectorySE3_l, trajectorySE3_r)
+    )
+    xs = np.array(ocp.solver.xs)
+    v_cmd = xs[1, robot.model.nq :]
+
+    if np.linalg.norm(v_cmd[:3]) < 0.05:
+        print(t, "RESOLVING FOR ONLY FINAL PATH POINT")
+        last_point_only = np.ones((len(trajectory_base), 2))
+        last_point_only = np.hstack(
+            (last_point_only, np.zeros((len(trajectory_base), 1)))
+        )
+        last_point_only = last_point_only * trajectory_base[-1]
+        ocp.warmstartAndReSolve(x0, data=(last_point_only))
+        xs = np.array(ocp.solver.xs)
+        v_cmd = xs[1, robot.model.nq :]
+
+    err_vector_ee_l = log6(robot.T_w_l.actInv(trajectorySE3_l[0]))
+    err_norm_ee_l = np.linalg.norm(err_vector_ee_l)
+    err_vector_ee_r = log6(robot.T_w_r.actInv(trajectorySE3_r[0]))
+    err_norm_ee_r = np.linalg.norm(err_vector_ee_r)
+    err_vector_base = np.linalg.norm(p - trajectory_base[0][:2])  # z axis is irrelevant
+    log_item = {}
+    log_item["err_vec_ee_l"] = err_vector_ee_l
+    log_item["err_norm_ee_l"] = err_norm_ee_l.reshape((1,))
+    log_item["err_vec_ee_r"] = err_vector_ee_r
+    log_item["err_norm_ee_r"] = err_norm_ee_r.reshape((1,))
+    log_item["err_norm_base"] = np.linalg.norm(err_vector_base).reshape((1,))
+    save_past_item = {"path2D": p}
+    return v_cmd, save_past_item, log_item
+
+
+def DualArmCartPulling(
+    args: Namespace,
+    robot: DualArmWholeBodyInterface,
+    path_planner: ProcessManager | types.FunctionType,
+    T_absgoal_l: SE3,
+    T_absgoal_r: SE3,
+    run=True,
+) -> None | ControlLoopManager:
+    """
+    CrocoEndEffectorPathFollowingMPC
+    -----
+    run mpc for a point-to-point inverse kinematics.
+    note that the actual problem is solved on
+    a dynamics level, and velocities we command
+    are actually extracted from the state x(q,dq).
+    """
+    robot._mode = DualArmWholeBodyInterface.control_mode.whole_body
+    # NOTE: i'm shoving these into the class here - bad style,
+    # but i don't
+    T_w_abs = robot.T_w_abs
+    x0 = np.concatenate([robot.q, robot.v])
+    ocp = BaseAndDualArmEEPathFollowingOCP(args, robot, x0)
+    ocp.solveInitialOCP(x0)
+
+    max_base_v = np.linalg.norm(robot._max_v[:2])
+
+    path2D_handlebar = initializePastData(args, T_w_abs, robot.q[:2], float(max_base_v))
+
+    get_position = lambda robot: robot.q[:2]
+    DualArmCartPullingControlLoop_with_l_r = partial(
+        DualArmCartPullingControlLoop, T_absgoal_l, T_absgoal_r
+    )
+    controlLoop = partial(
+        PathFollowingFromPlannerCtrllLoopTemplate,
+        path_planner,
+        get_position,
+        ocp,
+        DualArmCartPullingControlLoop_with_l_r,
+        args,
+        robot,
+    )
+
+    log_item = {
+        "qs": np.zeros(robot.model.nq),
+        "dqs": np.zeros(robot.model.nv),
+        "err_vec_ee_l": np.zeros((6,)),
+        "err_norm_ee_l": np.zeros((1,)),
+        "err_vec_ee_r": np.zeros((6,)),
+        "err_norm_ee_r": np.zeros((1,)),
+        "err_norm_base": np.zeros((1,)),
+    }
+    save_past_dict = {"path2D": T_w_abs.translation[:2]}
+    loop_manager = ControlLoopManager(
+        robot, controlLoop, args, save_past_dict, log_item
+    )
+    # actually put past data into the past window
+    loop_manager.past_data["path2D"].clear()
+    loop_manager.past_data["path2D"].extend(
+        path2D_handlebar[i] for i in range(args.past_window_size)
+    )
+
+    if run:
+        loop_manager.run()
+    else:
+        return loop_manager
diff --git a/examples/cart_pulling/control_sub_problems/fixed_path_planner.py b/examples/cart_pulling/control_sub_problems/fixed_path_planner.py
new file mode 100644
index 0000000000000000000000000000000000000000..0116bc4edd42787726ab00630d8fa55ea765dbda
--- /dev/null
+++ b/examples/cart_pulling/control_sub_problems/fixed_path_planner.py
@@ -0,0 +1,36 @@
+from smc.robots.abstract_robotmanager import AbstractRobotManager
+from smc.util.draw_path import drawPath
+
+from argparse import Namespace
+import matplotlib
+import numpy as np
+
+
+def contructPath(args: Namespace, robot: AbstractRobotManager):
+    if not args.draw_new:
+        pixel_path_file_path = "./parameters/path_in_pixels.csv"
+        path_base = np.genfromtxt(pixel_path_file_path, delimiter=",")
+    else:
+        matplotlib.use("tkagg")
+        path_base = drawPath(args)
+        matplotlib.use("qtagg")
+    path_base[:, 0] = path_base[:, 0] * args.map_width
+    path_base[:, 1] = path_base[:, 1] * args.map_height
+    path_base = np.hstack((path_base, np.zeros((len(path_base), 1))))
+    robot.updateViz({"fixed_path": path_base})
+    return path_base
+
+
+def fixedPathPlanner(
+    path_parameter: list[int],
+    path_base: np.ndarray,
+    p_base: np.ndarray,
+) -> np.ndarray:
+    distances = np.linalg.norm(p_base - path_base, axis=1)
+    index = np.argmin(distances)
+    # NOTE: bullshit heuristic to deal with the path intersecting with itself.
+    # i can't be bothered with anything better
+    if (index - path_parameter[0]) > 0 and (index - path_parameter[0]) < 10:
+        path_parameter[0] += 1
+    path_base = path_base[path_parameter[0] :]
+    return path_base
diff --git a/examples/cart_pulling/control_sub_problems/single_arm_cart_pulling_control_loop.py b/examples/cart_pulling/control_sub_problems/single_arm_cart_pulling_control_loop.py
index 312dbd7faf3eda2fa16bcd5ebd00282350d7537d..c395830be43ca44c49c6fd633c2dc24db5633ad7 100644
--- a/examples/cart_pulling/control_sub_problems/single_arm_cart_pulling_control_loop.py
+++ b/examples/cart_pulling/control_sub_problems/single_arm_cart_pulling_control_loop.py
@@ -66,6 +66,19 @@ def SingleArmCartPullingMPCControlLoop(
     xs = np.array(ocp.solver.xs)
     v_cmd = xs[1, robot.model.nq :]
 
+    # NOTE: if the base isn't moving, it's probably stuck in some local minimum
+    # this happens with just the navigation reference
+    if np.linalg.norm(v_cmd[:3]) < 0.05:
+        print(t, "RESOLVING FOR ONLY FINAL PATH POINT")
+        last_point_only = np.ones((len(trajectory_base), 2))
+        last_point_only = np.hstack(
+            (last_point_only, np.zeros((len(trajectory_base), 1)))
+        )
+        last_point_only = last_point_only * trajectory_base[-1]
+        ocp.warmstartAndReSolve(x0, data=(last_point_only))
+        xs = np.array(ocp.solver.xs)
+        v_cmd = xs[1, robot.model.nq :]
+
     err_vector_ee = log6(robot.T_w_e.actInv(trajectorySE3_handlebar[0]))
     err_vector_base = np.linalg.norm(p - trajectory_base[0][:2])  # z axis is irrelevant
     log_item = {}
diff --git a/examples/cart_pulling/control_sub_problems/utils.py b/examples/cart_pulling/control_sub_problems/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..88c5c23d59196d4339c655ffdf3b367b0abf51e9
--- /dev/null
+++ b/examples/cart_pulling/control_sub_problems/utils.py
@@ -0,0 +1,63 @@
+from smc.control.optimal_control.util import get_OCP_args
+from smc.path_generation.planner import getPlanningArgs
+from smc.control.cartesian_space import getClikArgs
+from smc import getMinimalArgParser
+
+import argparse
+from pinocchio import SE3
+import numpy as np
+
+
+def get_args() -> argparse.Namespace:
+    parser = getMinimalArgParser()
+    parser = get_OCP_args(parser)
+    parser = getClikArgs(parser)  # literally just for goal error
+    parser = getPlanningArgs(parser)
+    parser.add_argument(
+        "--handlebar-height",
+        type=float,
+        default=0.5,
+        help="heigh of handlebar of the cart to be pulled",
+    )
+    parser.add_argument(
+        "--base-to-handlebar-preferred-distance",
+        type=float,
+        default=0.5,
+        help="prefered path arclength from mobile base position to handlebar",
+    )
+    parser.add_argument(
+        "--planner",
+        action=argparse.BooleanOptionalAction,
+        help="if on, you're in a pre-set map and a planner produce a plan to navigate. if off, you draw the path to be followed",
+        default=True,
+    )
+    parser.add_argument(
+        "--draw-new",
+        action=argparse.BooleanOptionalAction,
+        help="are you drawing a new path or reusing the previous one",
+        default=False,
+    )
+    parser.add_argument(
+        "--map-width",
+        type=float,
+        help="width of the map in meters (x-axis) - only used for drawing of the path",
+        default=3.0,
+    )
+    parser.add_argument(
+        "--map-height",
+        type=float,
+        help="height of the map in meters (y-axis) - only used for drawing of the path",
+        default=3.0,
+    )
+    args = parser.parse_args()
+    return args
+
+
+def constructInitialT_w_abs(
+    args: argparse.Namespace, path_base: np.ndarray, rotation: np.ndarray
+) -> SE3:
+    direction = path_base[1] - path_base[0]
+    handlebar_direction = -1 * direction
+    handlebar_direction = handlebar_direction / np.linalg.norm(handlebar_direction)
+    offset = args.base_to_handlebar_preferred_distance * handlebar_direction
+    return SE3(rotation, path_base[0] + offset)
diff --git a/examples/navigation/mobile_base_navigation.py b/examples/navigation/mobile_base_navigation.py
index 7e0f97723cac66e32a71343e3686a4f7503c8983..8ab2d287959307213895169158bf680520c860a1 100644
--- a/examples/navigation/mobile_base_navigation.py
+++ b/examples/navigation/mobile_base_navigation.py
@@ -3,15 +3,16 @@ from smc import getMinimalArgParser
 from smc.control.control_loop_manager import ControlLoopManager
 from smc.robots.interfaces import MobileBaseInterface
 from smc.path_generation.maps.premade_maps import createSampleStaticMap
-from smc.path_generation.path_math.path2d_to_6d import path2D_to_SE3
 from smc.control.optimal_control.util import get_OCP_args
 from smc.control.cartesian_space import getClikArgs
 from smc.control.cartesian_space.cartesian_space_trajectory_following import (
     cartesianPathFollowingWithPlanner,
 )
 from smc.path_generation.planner import starPlanner, getPlanningArgs
-from smc.control.optimal_control.croco_mpc_point_to_point import CrocoBaseP2PMPC
-from smc.control.optimal_control.croco_mpc_path_following import (
+from smc.control.optimal_control.croco_point_to_point.mpc.base_reference_mpc import (
+    CrocoBaseP2PMPC,
+)
+from smc.control.optimal_control.croco_path_following.mpc.base_reference_mpc import (
     CrocoBasePathFollowingMPC,
 )
 from smc.multiprocessing import ProcessManager
@@ -57,7 +58,9 @@ def get_args() -> argparse.Namespace:
     return args
 
 
-def fixedPathPlanner(path2D: np.ndarray, p_base: np.ndarray) -> np.ndarray:
+def fixedPathPlanner(
+    path_parameter: list[int], path2D: np.ndarray, p_base: np.ndarray
+) -> np.ndarray:
     """
     fixedPathPlanner
     ----------------
@@ -75,7 +78,9 @@ def fixedPathPlanner(path2D: np.ndarray, p_base: np.ndarray) -> np.ndarray:
     p_base_3 = np.array([p_base[0], p_base[1], 0.0])
     distances = np.linalg.norm(p_base_3 - path2D, axis=1)
     index = np.argmin(distances)
-    path2D = path2D[index:]
+    if (index - path_parameter[0]) > 0 and (index - path_parameter[0]) < 10:
+        path_parameter[0] += 1
+    path2D = path2D[path_parameter[0] :]
     return path2D
 
 
@@ -154,7 +159,7 @@ if __name__ == "__main__":
         path2D = np.hstack((path2D, np.zeros((len(path2D), 1))))
         robot.updateViz({"fixed_path": path2D})
         CrocoBaseP2PMPC(args, robot, path2D[0])
-        path_planner = partial(fixedPathPlanner, path2D)
+        path_planner = partial(fixedPathPlanner, [0], path2D)
 
     CrocoBasePathFollowingMPC(args, robot, x0, path_planner)
     # cartesianPathFollowingWithPlanner(args, robot, path_planner)
diff --git a/python/smc/control/cartesian_space/pink_p2p.py b/python/smc/control/cartesian_space/pink_p2p.py
index 87526d5c9dc1b0683757fea1236916f1943d8c00..3322b726f4b385c841d01bbca8ef69e1d73e6720 100644
--- a/python/smc/control/cartesian_space/pink_p2p.py
+++ b/python/smc/control/cartesian_space/pink_p2p.py
@@ -1,5 +1,6 @@
 from smc.control.control_loop_manager import ControlLoopManager
 from smc.control.controller_templates.point_to_point import DualEEP2PCtrlLoopTemplate
+from smc.robots.abstract_robotmanager import AbstractRobotManager
 from smc.robots.interfaces.dual_arm_interface import DualArmInterface
 
 import pink
@@ -12,6 +13,7 @@ import argparse
 from functools import partial
 from collections import deque
 import pinocchio as pin
+from smc.robots.interfaces.mobile_base_interface import MobileBaseInterface
 
 
 # TODO: butcher pink to avoid redundancies with smc like configuration.
@@ -52,6 +54,10 @@ def DualArmIKSelfAvoidanceViaEndEffectorSpheresCtrlLoop(
         # safety_break=True,
         safety_break=False,
     )
+    # NOTE: this is a temporary solution to deal with the fact that model isn't a property depending on control mode yet
+    # TODO: make model a property depending on control mode to avoid this shitty issue
+    if robot.mode == AbstractRobotManager.control_mode.upper_body:
+        v_cmd[:3] = 0.0
     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
diff --git a/python/smc/control/optimal_control/croco_path_following/mpc/base_and_dual_arm_reference_mpc.py b/python/smc/control/optimal_control/croco_path_following/mpc/base_and_dual_arm_reference_mpc.py
index 4912177eedd7d80f1a09efa7150d8e6a6189f40b..88c465bdfb3527be77acf32bb17f76205227288e 100644
--- a/python/smc/control/optimal_control/croco_path_following/mpc/base_and_dual_arm_reference_mpc.py
+++ b/python/smc/control/optimal_control/croco_path_following/mpc/base_and_dual_arm_reference_mpc.py
@@ -3,16 +3,9 @@ from smc.robots.interfaces.whole_body_interface import (
 )
 from smc.control.control_loop_manager import ControlLoopManager
 from smc.multiprocessing.process_manager import ProcessManager
-from smc.control.optimal_control.abstract_croco_ocp import CrocoOCP
 from smc.control.optimal_control.croco_path_following.ocp.base_and_dual_arm_reference_ocp import (
     BaseAndDualArmEEPathFollowingOCP,
 )
-from smc.path_generation.path_math.path2d_to_6d import (
-    path2D_to_SE3,
-)
-from smc.path_generation.path_math.cart_pulling_path_math import (
-    construct_EE_path,
-)
 from smc.path_generation.path_math.path_to_trajectory import (
     path2D_to_trajectory2D,
     pathSE3_to_trajectorySE3,
@@ -29,25 +22,11 @@ from pinocchio import SE3, log6
 from collections import deque
 
 
-def initializePastData(
-    args: Namespace, T_w_e: SE3, p_base: np.ndarray, max_base_v: float
-) -> np.ndarray:
-    # prepopulate past data to make base and cart be on the same path in the past
-    # (which didn't actually happen because this just started)
-    p_ee = T_w_e.translation[:2]
-    straight_line_path = np.linspace(p_ee, p_base, args.past_window_size)
-    # straight_line_path_timed = path2D_timed(args, straight_line_path, max_base_v)
-    # return straight_line_path_timed # this one is shortened to args.n_knots! and we want the whole buffer
-    return straight_line_path
-
-
-# TODO: the robot put in is a whole body robot,
-# which you need to implement
 def BaseAndDualEEPathFollowingMPCControlLoop(
     T_absgoal_l: SE3,
     T_absgoal_r: SE3,
-    ocp: CrocoOCP,
-    path2D_base: np.ndarray,
+    ocp: BaseAndDualArmEEPathFollowingOCP,
+    path: tuple[np.ndarray, list[SE3]],
     args: Namespace,
     robot: DualArmWholeBodyInterface,
     t: int,
@@ -59,25 +38,24 @@ def BaseAndDualEEPathFollowingMPCControlLoop(
     cart pulling dual arm control loop
     """
     p = robot.q[:2]
+    path_base, path_T_w_abs = path
 
     # NOTE: this one is obtained as the future path from path planner
     max_base_v = np.linalg.norm(robot._max_v[:2])
-    trajectory_base = path2D_to_trajectory2D(args, path2D_base, max_base_v)
+    trajectory_base = path2D_to_trajectory2D(args, path_base, max_base_v)
     trajectory_base = np.hstack((trajectory_base, np.zeros((len(trajectory_base), 1))))
 
-    trajectorySE3_handlebar = construct_EE_path(args, p, past_data["path2D"])
+    trajectorySE3_T_w_abs = pathSE3_to_trajectorySE3(args, path_T_w_abs, max_base_v)
     trajectorySE3_l = []
     trajectorySE3_r = []
-    for traj_pose in trajectorySE3_handlebar:
+    for traj_pose in trajectorySE3_T_w_abs:
         trajectorySE3_l.append(T_absgoal_l.act(traj_pose))
         trajectorySE3_r.append(T_absgoal_r.act(traj_pose))
 
     if args.visualizer:
         if t % int(np.ceil(args.ctrl_freq / 25)) == 0:
             robot.visualizer_manager.sendCommand({"path": trajectory_base})
-            robot.visualizer_manager.sendCommand(
-                {"frame_path": trajectorySE3_handlebar}
-            )
+            robot.visualizer_manager.sendCommand({"frame_path": trajectorySE3_T_w_abs})
 
     x0 = np.concatenate([robot.q, robot.v])
     ocp.warmstartAndReSolve(
@@ -110,41 +88,31 @@ def BaseAndDualEEPathFollowingMPC(
     run=True,
 ) -> None | ControlLoopManager:
     """
-    CrocoEndEffectorPathFollowingMPC
-    -----
-    run mpc for a point-to-point inverse kinematics.
-    note that the actual problem is solved on
-    a dynamics level, and velocities we command
-    are actually extracted from the state x(q,dq).
+    BaseAndDualEEPathFollowingMPC
+    -------------------------------
+    path following with 3 refereces: base, left arm, right arm.
+    the path planner has to provide base path and T_w_abs path,
+    and T_absgoal_l and T_absgoal_r from which the left and right
+    references are constructed
     """
     robot._mode = DualArmWholeBodyInterface.control_mode.whole_body
-    # NOTE: i'm shoving these into the class here - bad style,
-    # but i don't
-    T_w_abs = robot.T_w_abs
     x0 = np.concatenate([robot.q, robot.v])
     ocp = BaseAndDualArmEEPathFollowingOCP(args, robot, x0)
     ocp.solveInitialOCP(x0)
 
-    max_base_v = np.linalg.norm(robot._max_v[:2])
-
-    path2D_handlebar = initializePastData(args, T_w_abs, robot.q[:2], float(max_base_v))
-
-    if type(path_planner) == types.FunctionType:
-        raise NotImplementedError
-    else:
-        get_position = lambda robot: robot.q[:2]
-        BaseAndDualEEPathFollowingMPCControlLoop_with_l_r = partial(
-            BaseAndDualEEPathFollowingMPCControlLoop, T_absgoal_l, T_absgoal_r
-        )
-        controlLoop = partial(
-            PathFollowingFromPlannerCtrllLoopTemplate,
-            path_planner,
-            get_position,
-            ocp,
-            BaseAndDualEEPathFollowingMPCControlLoop_with_l_r,
-            args,
-            robot,
-        )
+    get_position = lambda robot: robot.q[:2]
+    BaseAndDualEEPathFollowingMPCControlLoop_with_l_r = partial(
+        BaseAndDualEEPathFollowingMPCControlLoop, T_absgoal_l, T_absgoal_r
+    )
+    controlLoop = partial(
+        PathFollowingFromPlannerCtrllLoopTemplate,
+        path_planner,
+        get_position,
+        ocp,
+        BaseAndDualEEPathFollowingMPCControlLoop_with_l_r,
+        args,
+        robot,
+    )
 
     log_item = {
         "qs": np.zeros(robot.model.nq),
@@ -155,15 +123,7 @@ def BaseAndDualEEPathFollowingMPC(
         "err_norm_ee_r": np.zeros((1,)),
         "err_norm_base": np.zeros((1,)),
     }
-    save_past_dict = {"path2D": T_w_abs.translation[:2]}
-    loop_manager = ControlLoopManager(
-        robot, controlLoop, args, save_past_dict, log_item
-    )
-    # actually put past data into the past window
-    loop_manager.past_data["path2D"].clear()
-    loop_manager.past_data["path2D"].extend(
-        path2D_handlebar[i] for i in range(args.past_window_size)
-    )
+    loop_manager = ControlLoopManager(robot, controlLoop, args, {}, log_item)
 
     if run:
         loop_manager.run()
diff --git a/python/smc/control/optimal_control/croco_path_following/mpc/dual_arm_reference_mpc.py b/python/smc/control/optimal_control/croco_path_following/mpc/dual_arm_reference_mpc.py
index a96db7664fcdcf64b95edd11895eb9b90cbef941..fc24213e26eb8f23d8b0f64ba5a1a4b4d18bf14a 100644
--- a/python/smc/control/optimal_control/croco_path_following/mpc/dual_arm_reference_mpc.py
+++ b/python/smc/control/optimal_control/croco_path_following/mpc/dual_arm_reference_mpc.py
@@ -34,7 +34,7 @@ def CrocoDualArmEEPathFollowingMPCControlLoop(
     _: dict[str, deque[np.ndarray]],
 ) -> tuple[np.ndarray, dict[str, np.ndarray], dict[str, np.ndarray]]:
     """
-    CrocoEEPathFollowingMPCControlLoop
+    CrocoDualArmEEPathFollowingMPCControlLoop
     -----------------------------
     end-effectors follows the prescribed path.
     the path is defined with T_w_abs, from which
diff --git a/python/smc/robots/implementations/mobile_yumi.py b/python/smc/robots/implementations/mobile_yumi.py
index 1e76a5c00d73fa81a9a0bb6a088aadd358b3b183..8d142e13887393f15f53fdeca0461d39b8db0bab 100644
--- a/python/smc/robots/implementations/mobile_yumi.py
+++ b/python/smc/robots/implementations/mobile_yumi.py
@@ -20,9 +20,9 @@ class AbstractMobileYuMiRobotManager(DualArmWholeBodyInterface):
             get_mobile_yumi_model()
         )
 
-        self._l_ee_frame_id = self.model.getFrameId("robl_tool0")
-        self._r_ee_frame_id = self.model.getFrameId("robr_tool0")
-        self._base_frame_id = self.model.getFrameId("mobile_base")
+        self._l_ee_frame_id = self._model.getFrameId("robl_tool0")
+        self._r_ee_frame_id = self._model.getFrameId("robr_tool0")
+        self._base_frame_id = self._model.getFrameId("mobile_base")
         # TODO: CHANGE THIS TO REAL VALUES
         self._MAX_ACCELERATION = 1.7  # const
         self._MAX_QD = 3.14  # const
@@ -32,10 +32,10 @@ class AbstractMobileYuMiRobotManager(DualArmWholeBodyInterface):
         )
         self._comfy_configuration = np.array(
             [
-                0.0, # x 
-                0.0, # y
-                1.0, # cos(theta)
-                0.0, # sin(theta)
+                0.0,  # x
+                0.0,  # y
+                1.0,  # cos(theta)
+                0.0,  # sin(theta)
                 0.045,
                 -0.155,
                 -0.394,
diff --git a/python/smc/robots/interfaces/whole_body_interface.py b/python/smc/robots/interfaces/whole_body_interface.py
index 6e81f41f01890349c1640bf1b2bf6b2ed0eca13d..da3800866567254e26900d7f489a1409fe879de9 100644
--- a/python/smc/robots/interfaces/whole_body_interface.py
+++ b/python/smc/robots/interfaces/whole_body_interface.py
@@ -6,7 +6,7 @@ from smc.robots.interfaces.dual_arm_interface import DualArmInterface
 import pinocchio as pin
 from argparse import Namespace
 import numpy as np
-from enum import Enum
+from copy import deepcopy
 
 # TODO: put back in when python3.12 will be safe to use
 # from typing import override
@@ -158,7 +158,20 @@ class DualArmWholeBodyInterface(DualArmInterface, MobileBaseInterface):
             AbstractRobotManager.control_mode.left_arm_only,
             AbstractRobotManager.control_mode.right_arm_only,
         ]
+#        self._full_model = deepcopy(self._model)
+#        self._base_only_model = pin.buildReducedModel(self._model, [i for i in range(1, self._model.njoints + 1) if i > 1], np.zeros(self._model.nq))
+#        self._upper_body_model = pin.buildReducedModel(self._model, [i for i in range(1, self._model.njoints + 1) if i < 2], np.zeros(self._model.nq))
         super().__init__(args)
+    
+#    @property
+#    def model(self) -> pin.Model:
+#        if self.control_mode == AbstractRobotManager.control_mode.whole_body:
+#            return self._full_model
+#        if self.control_mode == AbstractRobotManager.control_mode.base_only:
+#            return self._base_only_model
+#        if self.control_mode == AbstractRobotManager.control_mode.upper_body:
+#            return self._upper_body_model
+#        return self._full_model
 
     # TODO: override model property to produce the reduced version
     # depending on the control mode.
diff --git a/python/smc/visualization/meshcat_viewer_wrapper/visualizer.py b/python/smc/visualization/meshcat_viewer_wrapper/visualizer.py
index 9a506ea3c939d77d535d7d7a473b60c143560766..90e833ee646d86ad4c0fefbc6304e3afc2032f2d 100644
--- a/python/smc/visualization/meshcat_viewer_wrapper/visualizer.py
+++ b/python/smc/visualization/meshcat_viewer_wrapper/visualizer.py
@@ -43,7 +43,9 @@ class MeshcatVisualizer(PMV):
         # which will never be changed
         self.n_points = 0
         self.n_path_points = 0
+        self.add_path_points_set = set()
         self.n_frame_path_points = 0
+        self.add_frame_path_points_set = set()
         self.n_obstacles = 0
         if robot is not None:
             super().__init__(robot.model, robot.collision_model, robot.visual_model)
@@ -109,32 +111,41 @@ class MeshcatVisualizer(PMV):
     def addPath(
         self, name, path: list[pin.SE3] | np.ndarray, radius=5e-3, color=[0, 0, 1, 0.8]
     ):
-        # who cares about the name
-        self.n_path_points = len(path)
+        # NOTE: there's too much of them so the visualization is slow
+        self.n_path_points = len(path) if len(path) < 5 else 5
         if name == "fixed_path":
-            color=[1, 0, 0, 0.8]
+            color = [1, 0, 0, 0.8]
         if type(path) == np.ndarray:
             # complete the quartenion
-            path = np.hstack((path, np.zeros((len(path), 3))))
-            path = np.hstack((path, np.ones((len(path), 1))))
-        for i, point in enumerate(path):
-            if i < self.n_path_points:
+            path_viz = np.hstack((path, np.zeros((len(path), 3))))
+            path_viz = np.hstack((path_viz, np.ones((len(path), 1))))
+        else:
+            path_viz = path.copy()
+        index_step = len(path_viz) // 5
+        for i, point in enumerate(path_viz):
+            if (i % index_step != 0) and name != "fixed_path":
+                continue
+            if i not in self.add_path_points_set:
                 self.addSphere(f"world/path_{name}_point_{i}", radius, color)
+                self.add_path_points_set.add(i)
             self.applyConfiguration(f"world/path_{name}_point_{i}", point)
-        self.n_path_points = max(i, self.n_path_points)
+        # self.n_path_points = max(i, self.n_path_points)
 
     def addFramePath(
         self, name, path: list[pin.SE3], radius=5e-3, color=[1, 0, 0, 0.8]
     ):
-        # who cares about the name
-        name = "frame_path"
+        self.n_frame_path_points = len(path) if len(path) < 5 else 5
+        index_step = len(path) // 5
         for i, point in enumerate(path):
-            if i < self.n_frame_path_points:
+            if (i % index_step != 0) and name != "fixed_frame_path":
+                continue
+            if i not in self.add_frame_path_points_set:
                 meshcat_shapes.frame(
                     self.viewer[f"world/frame_path_{name}_point_{i}"], opacity=0.3
                 )
+                self.add_frame_path_points_set.add(i)
             self.applyConfiguration(f"world/frame_path_{name}_point_{i}", point)
-        self.n_frame_path_points = max(i, self.n_frame_path_points)
+        # self.n_frame_path_points = max(i, self.n_frame_path_points)
 
     def applyConfiguration(self, name, placement):
         if isinstance(placement, list) or isinstance(placement, tuple):