diff --git a/python/examples/cart_pulling.py b/python/examples/cart_pulling.py
index 3faa03fd887c0f00fbb5baa9c001cc45b08dc145..8115932811e2caa1790f4d9a86a9fec630330b50 100644
--- a/python/examples/cart_pulling.py
+++ b/python/examples/cart_pulling.py
@@ -10,7 +10,7 @@ from ur_simple_control.optimal_control.crocoddyl_mpc import *
 from ur_simple_control.basics.basics import followKinematicJointTrajP
 from ur_simple_control.util.logging_utils import LogManager
 from ur_simple_control.visualize.visualize import plotFromDict
-from ur_simple_control.clik.clik import getClikArgs, cartesianPathFollowingWithPlanner, controlLoopClik, invKinmQP, dampedPseudoinverse
+from ur_simple_control.clik.clik import getClikArgs, cartesianPathFollowingWithPlanner, controlLoopClik, invKinmQP, dampedPseudoinverse, controlLoopClikArmOnly
 import pinocchio as pin
 import crocoddyl
 from functools import partial
@@ -56,6 +56,26 @@ def isGraspOK(args, robot, grasp_pose):
         isOK = True
     return not isOK 
 
+
+def isGripperRelativeToBaseOK(args, robot):
+    isOK = False
+    # we want to be in the back of the base (x-axis) and on handlebar height
+    T_w_base = robot.data.oMi[1]
+    # rotation for the gripper is base with z flipped to point into the ground
+    rotate = pin.SE3(pin.rpy.rpyToMatrix(np.pi, 0.0, 0.0), np.zeros(3))
+    # translation is prefered distance from base
+    translate = pin.SE3(np.eye(3), np.array([args.base_to_handlebar_preferred_distance, 0.0, args.handlebar_height]))
+    #grasp_pose = T_w_base.act(rotate.act(translate))
+    grasp_pose = T_w_base.act(translate.act(rotate))
+    SEerror = robot.getT_w_e().actInv(grasp_pose)
+    err_vector = pin.log6(SEerror).vector 
+    # TODO: figure this out
+    # it seems you have to use just the arm to get to finish with this precision 
+    #if np.linalg.norm(err_vector) < robot.args.goal_error:
+    if np.linalg.norm(err_vector) < 2*1e-1:
+        isOK = True
+    return isOK, grasp_pose 
+
 def cartPullingControlLoop(args, robot : RobotManager, goal, solver_grasp, solver_pulling,
                            path_planner : ProcessManager, i : int, past_data):
     """
@@ -75,17 +95,14 @@ def cartPullingControlLoop(args, robot : RobotManager, goal, solver_grasp, solve
     priority_register = ['0','1','1']
     # TODO implement this based on laser scanning or whatever
     #priority_register[0] = str(int(areObstaclesTooClose()))
+    graspOK, grasp_pose = isGripperRelativeToBaseOK(args, robot)
+    # NOTE: this keeps getting reset after initial grasp has been completed.
+    # and we want to let mpc cook
+    priority_register[1] = str(int(not graspOK)) # set if not ok
     # TODO: get grasp pose from vision, this is initial hack to see movement
-    if i < 300:
-        rotation = np.eye(3)
-        rotation[1][1] = -1.0
-        rotation[2][2] = -1.0
-        translation = np.array([10.0 + args.base_to_handlebar_preferred_distance, 4.0, args.handlebar_height])
-        grasp_pose = pin.SE3(rotation, translation)
-        priority_register[1] = str(int(isGraspOK(args, robot, grasp_pose)))
-    else:
-        grasp_pose = robot.getT_w_e()
-        priority_register[1] = '0'
+#    if i > 1000:
+#        priority_register[1] = '0'
+    
     # interpret string as base 2 number, return int in base 10
     priority_int = ""
     for prio in priority_register:
@@ -116,7 +133,8 @@ def cartPullingControlLoop(args, robot : RobotManager, goal, solver_grasp, solve
         else:
             #controlLoopClik(robot, invKinmQP, i, past_data)
             clikController = partial(dampedPseudoinverse, 1e-3)
-            controlLoopClik(robot, clikController, i, past_data)
+            #controlLoopClik(robot, clikController, i, past_data)
+            controlLoopClikArmOnly(robot, clikController, i, past_data)
 
     # case 2)
     # MASSIVE TODO: 
@@ -126,18 +144,23 @@ def cartPullingControlLoop(args, robot : RobotManager, goal, solver_grasp, solve
     # whether we're transitioning from cliking to pulling
     # this is the time and place to populate the past path from the pulling to make sense
     if past_data['priority_register'][-1][1] == '1' and priority_register[1] == '0': 
-    #if True: 
-        # this is not the right spacing
-        # TODO: this ain't right
+        # create straight line path from ee to base
         p_cart = q[:2]
         p_ee = T_w_e.translation[:2]
         straigh_line_path = np.linspace(p_ee, p_cart, args.past_window_size)
+        # time it the same way the base path is timed 
+        time_past = np.linspace(0.0, args.past_window_size * robot.dt, args.past_window_size)
+        s = np.linspace(0.0, args.n_knots * args.ocp_dt, args.past_window_size)
+        path2D_handlebar = np.hstack((
+            np.interp(s, time_past, straigh_line_path[:,0]).reshape((-1,1)), 
+            np.interp(s, time_past, straigh_line_path[:,1]).reshape((-1,1))))
+
         past_data['path2D_untimed'].clear()
-        past_data['path2D_untimed'].extend(straigh_line_path[i] for i in range(args.past_window_size))
+        past_data['path2D_untimed'].extend(path2D_handlebar[i] for i in range(args.past_window_size))
 
     if priority_int < 2:
         # TODO make this one work
-        breakFlag, save_past_item, log_item = BaseAndEEPathFollowingMPCControlLoop(args, robot, solver_pulling, path_planner, i, past_data)
+        breakFlag, save_past_item, log_item = BaseAndEEPathFollowingMPCControlLoop(args, robot, solver_pulling, path_planner, grasp_pose, i, past_data)
         #BaseAndEEPathFollowingMPCControlLoop(args, robot, solver_pulling, path_planner, i, past_data)
 
     p = q[:2]
diff --git a/python/ur_simple_control/clik/clik.py b/python/ur_simple_control/clik/clik.py
index 5fc227209086cfad64e7d419604fa8ab43afd51a..a9eb3f1f591c6d984b1594473aff140ff612f947 100644
--- a/python/ur_simple_control/clik/clik.py
+++ b/python/ur_simple_control/clik/clik.py
@@ -187,6 +187,31 @@ def controlLoopClik(robot : RobotManager, clik_controller, i, past_data):
     # hence the empty dict
     return breakFlag, {}, log_item
 
+def controlLoopClikArmOnly(robot, clik_controller, i, past_data):
+    breakFlag = False
+    log_item = {}
+    q = robot.getQ()
+    T_w_e = robot.getT_w_e()
+    # first check whether we're at the goal
+    SEerror = T_w_e.actInv(robot.Mgoal)
+    err_vector = pin.log6(SEerror).vector 
+    if np.linalg.norm(err_vector) < robot.args.goal_error:
+        breakFlag = True
+    J = pin.computeFrameJacobian(robot.model, robot.data, q, robot.ee_frame_id)
+    # cut off base from jacobian
+    J = J[:,3:]
+    # compute the joint velocities based on controller you passed
+    qd = clik_controller(J, err_vector)
+    # add the missing velocities back
+    qd = np.hstack((np.zeros(3), qd))
+    robot.sendQd(qd)
+    
+    log_item['qs'] = q.reshape((robot.model.nq,))
+    log_item['dqs'] = robot.getQd().reshape((robot.model.nv,))
+    log_item['dqs_cmd'] = qd.reshape((robot.model.nv,))
+    # we're not saving here, but need to respect the API, 
+    # hence the empty dict
+    return breakFlag, {}, log_item
 
 def moveUntilContactControlLoop(args, robot : RobotManager, speed, clik_controller, i, past_data):
     """
diff --git a/python/ur_simple_control/optimal_control/crocoddyl_mpc.py b/python/ur_simple_control/optimal_control/crocoddyl_mpc.py
index 26571a05dfcbcc577633cc6e28e9acc5fedbaaa2..65d3858af6cf57d6cd82e4117b1373ae180cc5e5 100644
--- a/python/ur_simple_control/optimal_control/crocoddyl_mpc.py
+++ b/python/ur_simple_control/optimal_control/crocoddyl_mpc.py
@@ -213,7 +213,8 @@ def CrocoEndEffectorPathFollowingMPC(args, robot, x0, path_planner):
     loop_manager.run()
 
 
-def BaseAndEEPathFollowingMPCControlLoop(args, robot : RobotManager, solver : crocoddyl.SolverBoxFDDP, path_planner : ProcessManager, iter_n, past_data):
+def BaseAndEEPathFollowingMPCControlLoop(args, robot : RobotManager, solver : crocoddyl.SolverBoxFDDP, path_planner : ProcessManager, 
+                                         grasp_pose, iter_n, past_data):
     """
     CrocoPathFollowingMPCControlLoop
     -----------------------------
diff --git a/python/ur_simple_control/optimal_control/crocoddyl_optimal_control.py b/python/ur_simple_control/optimal_control/crocoddyl_optimal_control.py
index 3060ceb54148de68f2629836e7769c1fb7d51163..c7d2828b857f6c62dbc4e3fcb8ca9a3e3e497834 100644
--- a/python/ur_simple_control/optimal_control/crocoddyl_optimal_control.py
+++ b/python/ur_simple_control/optimal_control/crocoddyl_optimal_control.py
@@ -498,14 +498,14 @@ def createBaseAndEEPathFollowingOCP(args, robot : RobotManager, x0):
                          # NOTE: this should be done outside of this function to clarity
                 state.nv)
         eeTrackingCost = crocoddyl.CostModelResidual(state, eePoseResidual)
-        runningCostModel.addCost("ee_pose" + str(i), eeTrackingCost, 1e2)
+        runningCostModel.addCost("ee_pose" + str(i), eeTrackingCost, args.ee_pose_cost)
         baseTranslationResidual = crocoddyl.ResidualModelFrameTranslation(
                 state,
                 robot.model.getFrameId("mobile_base"),
                 path_base[i],
                 state.nv)
         baseTrackingCost = crocoddyl.CostModelResidual(state, baseTranslationResidual)
-        runningCostModel.addCost("base_translation" + str(i), baseTrackingCost, 1e2)
+        runningCostModel.addCost("base_translation" + str(i), baseTrackingCost, args.base_translation_cost)
 
         if args.solver == "boxfddp":
             runningModel = crocoddyl.IntegratedActionModelEuler(
@@ -543,8 +543,8 @@ def createBaseAndEEPathFollowingOCP(args, robot : RobotManager, x0):
                 0.0,
             )
 
-    terminalCostModel.addCost("ee_pose" + str(args.n_knots), eeTrackingCost, 1e2)
-    terminalCostModel.addCost("base_translation" + str(args.n_knots), baseTrackingCost, 1e2)
+    terminalCostModel.addCost("ee_pose" + str(args.n_knots), eeTrackingCost, args.ee_pose_cost)
+    terminalCostModel.addCost("base_translation" + str(args.n_knots), baseTrackingCost, args.base_translation_cost)
 
     # now we define the problem
     problem = crocoddyl.ShootingProblem(x0, runningModels, terminalModel)
diff --git a/python/ur_simple_control/optimal_control/get_ocp_args.py b/python/ur_simple_control/optimal_control/get_ocp_args.py
index 97d87a1f98817979448d3c57ec6fe1304ffd13c7..177b2cf490781465d61cf999a40efd06aaa6235c 100644
--- a/python/ur_simple_control/optimal_control/get_ocp_args.py
+++ b/python/ur_simple_control/optimal_control/get_ocp_args.py
@@ -12,4 +12,8 @@ def get_OCP_args(parser : argparse.ArgumentParser):
                         help="optimal control problem solver you want", choices=["boxfddp", "csqp"])
     parser.add_argument("--max-solver-iter", type=int, default=200, \
                         help="number of iterations the ocp solver takes. ~100-500 for just ocp (it can converge before ofc), ~10 for mpc")
+    parser.add_argument("--ee-pose-cost", type=float, default=1e2, \
+                        help="ee pose cost in pulling mpc")
+    parser.add_argument("--base-translation-cost", type=float, default=1e2, \
+                        help="base translation cost in pulling mpc")
     return parser
diff --git a/python/ur_simple_control/path_generation/planner.py b/python/ur_simple_control/path_generation/planner.py
index 0fc4397867432a4ab28e1eaa7818cda03dcf7f9c..9d63181379b28f43c03be7bf490f0e9d9361937d 100644
--- a/python/ur_simple_control/path_generation/planner.py
+++ b/python/ur_simple_control/path_generation/planner.py
@@ -578,6 +578,7 @@ def path2D_to_SE3(path2D, path_height):
     pathSE3 = []
     for i in range(len(path2D) - 1):
         # first set the x axis to be in the theta direction
+        # TODO: make sure this one makes sense
         rotation = np.array([
                     [-np.cos(thetas[i]), np.sin(thetas[i]), 0.0],
                     [-np.sin(thetas[i]), -np.cos(thetas[i]), 0.0],