diff --git a/development_plan.toml b/development_plan.toml
index 8c4361b4572fc1934bff8669b6098c41ed7344cb..c6cd1e0863d44bba8d34d535f2628f584e351d12 100644
--- a/development_plan.toml
+++ b/development_plan.toml
@@ -94,7 +94,7 @@ dependencies = ["create/fix type-checking workflow"]
 purpose = """easier code writting and debugging, making library professional-grade"""
 # -1 means it should be a sum over sub-items
 hours_required = 8
-is_done = false
+is_done = true
 
 [[project_plan.action_items.action_items]]
 name = "robotmanager abstract class"
@@ -168,7 +168,19 @@ deadline = 2025-01-31
 dependencies = ["robotmanager abstract class"]
 purpose = """making it easy, simple, and error-abstinent to only navigate the base"""
 hours_required = 4
-is_done = false
+is_done = true
+
+[[project_plan.action_items.action_items]]
+name = "whole-body feature"
+description = """
+a generic class that has at least 1 manipulator plus a mobile base. what you do here is give me access to just the arm or just the base - that's it. you can do it by overriding setters or whatever. you don't need to finish it, it just needs to do what's necessary for cart pulling
+"""
+priority = 1
+deadline = 2025-01-31
+dependencies = ["robotmanager abstract class", "mobile_base_feature", "dual_arms_feature"]
+purpose = """making it easy, simple, and error-abstinent to only navigate the base"""
+hours_required = 4
+is_done = true
 
 
 [[project_plan.action_items.action_items]]
@@ -289,6 +301,18 @@ street cred"""
 hours_required = 80
 is_done = false
 
+[[project_plan.action_items.action_items]]
+name = "tidy up typing"
+description = """
+sit down and get typing coverage to 100% or whatever is the limit of reason.
+"""
+priority = 3
+deadline = 2025-09-01
+dependencies = ["create/fix type-checking workflow"]
+purpose = """easier code writting and debugging, making library professional-grade"""
+# -1 means it should be a sum over sub-items
+hours_required = 8
+is_done = true
 
 [[project_plan.action_items]]
 name = "python stdlib logging instead of printing"
diff --git a/examples/cart_pulling/path_following_from_planner.py b/examples/cart_pulling/path_following_from_planner.py
index 8470014f5bbff713a4a54552c6eed5b33ef36788..e3b7f77b06fd755b7563fa5ad6c583bf6ecda658 100644
--- a/examples/cart_pulling/path_following_from_planner.py
+++ b/examples/cart_pulling/path_following_from_planner.py
@@ -1,9 +1,11 @@
 from smc import getRobotFromArgs
 from smc import getMinimalArgParser
 from smc.path_generation.maps.premade_maps import createSampleStaticMap
+from smc.path_generation.path_math.path2d_to_6d import path2D_to_SE3_fixed
 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 CrocoIKMPC
 from smc.control.optimal_control.croco_mpc_path_following import (
     CrocoEndEffectorPathFollowingMPC,
 )
@@ -12,6 +14,7 @@ from smc.multiprocessing import ProcessManager
 import time
 import numpy as np
 from functools import partial
+import pinocchio as pin
 
 
 def get_args():
@@ -32,19 +35,44 @@ def get_args():
 if __name__ == "__main__":
     args = get_args()
     robot = getRobotFromArgs(args)
-    #    robot._q[0] = 9.0
-    #    robot._q[1] = 4.0
+    # TODO: HOW IS IT POSSIBLE THAT T_W_E IS WRONG WITHOUT STEP CALLED HERE?????????????????
+    robot._step()
+    T_w_e = robot.T_w_e
+    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])
 
     planning_function = partial(starPlanner, goal)
-    # TODO: ensure alignment in orientation between planner and actual robot
-    path_planner = ProcessManager(args, planning_function, robot.q[:2], 3, None)
+    # here we're following T_w_e reference so that's what we send
+    path_planner = ProcessManager(
+        args, planning_function, T_w_e.translation[:2], 3, None
+    )
     _, map_as_list = createSampleStaticMap()
     if args.visualizer:
         robot.sendRectangular2DMapToVisualizer(map_as_list)
-        time.sleep(5)
+        # time.sleep(5)
+
+    T_w_e = robot.T_w_e
+    data = None
+    # get first path
+    while data is None:
+        path_planner.sendCommand(T_w_e.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_fixed(path2D, args.handlebar_height)
+    if args.visualizer:
+        # TODO: document this somewhere
+        robot.visualizer_manager.sendCommand({"Mgoal": pathSE3[0]})
+    if np.linalg.norm(pin.log6(T_w_e.actInv(pathSE3[0]))) > 1e-2:
+        print("going to initial path position")
+        CrocoIKMPC(args, robot, pathSE3[0])
+    print("initialized!")
     CrocoEndEffectorPathFollowingMPC(args, robot, x0, path_planner)
 
     print("final position:", robot.T_w_e)
diff --git a/python/smc/control/optimal_control/abstract_croco_ocp.py b/python/smc/control/optimal_control/abstract_croco_ocp.py
index 59a267fdc2839ca82ea83931f747ddfb9e0db901..0d4fa0f124a0a93b65eae6d8bde07763005abab9 100644
--- a/python/smc/control/optimal_control/abstract_croco_ocp.py
+++ b/python/smc/control/optimal_control/abstract_croco_ocp.py
@@ -135,8 +135,6 @@ class CrocoOCP(abc.ABC):
         ):
             self.xlb = self.xlb[1:]
             self.xub = self.xub[1:]
-        print(self.xlb)
-        print(self.xub)
 
     def constructConstraints(self) -> None:
         if self.solver_name == "boxfddp":
diff --git a/python/smc/control/optimal_control/croco_mpc_path_following.py b/python/smc/control/optimal_control/croco_mpc_path_following.py
index 1f1338c20ee3cfece648c6e9dcba1b91cfe8c273..222dd98db36b8bae6833bb3824a40838b9ec7d83 100644
--- a/python/smc/control/optimal_control/croco_mpc_path_following.py
+++ b/python/smc/control/optimal_control/croco_mpc_path_following.py
@@ -67,25 +67,27 @@ def CrocoEndEffectorPathFollowingMPCControlLoop(
             if args.debug_prints:
                 print("CTRL: got no path so i won't move")
             robot.sendVelocityCommand(np.zeros(robot.model.nv))
-            log_item["qs"] = q.reshape((robot.model.nq,))
-            log_item["dqs"] = robot.v.reshape((robot.model.nv,))
+            log_item["qs"] = q
+            log_item["dqs"] = robot.v
             return breakFlag, save_past_item, log_item
 
         if data == "done":
             breakFlag = True
+            robot.sendVelocityCommand(np.zeros(robot.model.nv))
+            log_item["qs"] = q
+            log_item["dqs"] = robot.v
+            return breakFlag, save_past_item, log_item
 
-        path_pol, path2D_untimed = data
+        _, path2D_untimed = data
         path2D_untimed = np.array(path2D_untimed).reshape((-1, 2))
         # who cares if the velocity is right,
         # it should be kinda right so that we need less ocp iterations
         # and that's it
-        max_base_v = np.linalg.norm(robot.model.velocityLimit[:2])
+        max_base_v = np.linalg.norm(robot._max_v[:2])
         path2D = path2D_timed(args, path2D_untimed, max_base_v)
 
         # create a 3D reference out of the path
         pathSE3 = path2D_to_SE3_fixed(path2D, args.handlebar_height)
-        print("following from", pathSE3[0])
-        exit()
 
     # TODO: EVIL AND HAS TO BE REMOVED FROM HERE
     if args.visualizer:
@@ -136,29 +138,6 @@ def CrocoEndEffectorPathFollowingMPC(
     the path does NOT need to start from your current pose - you need to get to it yourself.
     """
 
-    T_w_e = robot.T_w_e
-    data = None
-    # get first path
-    while data is None:
-        path_planner.sendCommand(T_w_e.translation[:2])
-        data = path_planner.getData()
-        # time.sleep(1 / args.ctrl_freq)
-        time.sleep(1)
-
-    _, path2D = data
-    print(path2D[0])
-    path2D = np.array(path2D).reshape((-1, 2))
-    pathSE3 = path2D_to_SE3_fixed(path2D, args.handlebar_height)
-    if args.visualizer:
-        # TODO: document this somewhere
-        robot.visualizer_manager.sendCommand({"Mgoal": pathSE3[0]})
-    print("i'm at", T_w_e)
-    print("going to", pathSE3[0])
-    if np.linalg.norm(pin.log6(T_w_e.actInv(pathSE3[0]))) > 1e-2:
-        print("running")
-        print("error", np.linalg.norm(pin.log6(T_w_e.actInv(pathSE3[0]))))
-        CrocoIKMPC(args, robot, pathSE3[0])
-    print("initialized!")
     ocp = CrocoEEPathFollowingOCP(args, robot, x0)
     solver = ocp.getSolver()
 
diff --git a/python/smc/control/optimal_control/croco_mpc_point_to_point.py b/python/smc/control/optimal_control/croco_mpc_point_to_point.py
index 30882035a5c396066e0825852da8d26be3522354..02bc018de3db160b9d8a4cb00e635037be6eda2b 100644
--- a/python/smc/control/optimal_control/croco_mpc_point_to_point.py
+++ b/python/smc/control/optimal_control/croco_mpc_point_to_point.py
@@ -50,8 +50,9 @@ def CrocoIKMPCControlLoop(args, robot: SingleArmInterface, solver, T_w_goal, _,
 
     log_item["qs"] = x0[: robot.model.nq]
     log_item["dqs"] = x0[robot.model.nq :]
-    log_item["dqs_cmd"] = vel_cmds
+    # log_item["dqs_cmd"] = vel_cmds
     log_item["u_tau"] = us[0]
+    log_item["err_norm"] = np.linalg.norm(err_vector).reshape((1,))
 
     return breakFlag, save_past_dict, log_item
 
@@ -80,8 +81,9 @@ def CrocoIKMPC(args, robot, T_w_goal, run=True):
     log_item = {
         "qs": np.zeros(robot.model.nq),
         "dqs": np.zeros(robot.model.nv),
-        "dqs_cmd": np.zeros(robot.model.nv),  # we're assuming full actuation here
+        #    "dqs_cmd": np.zeros(robot.model.nv),  # we're assuming full actuation here
         "u_tau": np.zeros(robot.model.nv),
+        "err_norm": np.zeros(1),
     }
     save_past_dict = {}
     loop_manager = ControlLoopManager(
diff --git a/python/smc/path_generation/path_math/path2d_to_6d.py b/python/smc/path_generation/path_math/path2d_to_6d.py
index a90b9c5fa66a3aff4b3c007cf13bc09722095c68..7687282479a8110a2370d3018a9804ebc24225f5 100644
--- a/python/smc/path_generation/path_math/path2d_to_6d.py
+++ b/python/smc/path_generation/path_math/path2d_to_6d.py
@@ -35,8 +35,8 @@ def path2D_to_SE3_fixed(path2D: np.ndarray, path_height: float):
         # 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],
+                [np.cos(thetas[i]), np.sin(thetas[i]), 0.0],
+                [np.sin(thetas[i]), -np.cos(thetas[i]), 0.0],
                 [0.0, 0.0, -1.0],
             ]
         )
diff --git a/python/smc/path_generation/path_math/path_to_trajectory.py b/python/smc/path_generation/path_math/path_to_trajectory.py
index 743f4c4c1b9c5e2886a59d4feb6172224574ca31..dace0769317a4bd7b436e8702fd3529ee26559e4 100644
--- a/python/smc/path_generation/path_math/path_to_trajectory.py
+++ b/python/smc/path_generation/path_math/path_to_trajectory.py
@@ -36,7 +36,7 @@ def path2D_timed(args, path2D_untimed, max_base_v):
     # the strategy is somewhat reasonable at least:
     # assume we're moving at 90% max velocity in the base,
     # and use that.
-    perc_of_max_v = 0.9
+    perc_of_max_v = 0.5
     base_v = perc_of_max_v * max_base_v
 
     # 1) the length of the path divided by 0.9 * max_vel
@@ -69,11 +69,11 @@ def path2D_timed(args, path2D_untimed, max_base_v):
     # time is i * args.ocp_dt
     for i in range(args.n_knots + 1):
         # what it should be but gets stuck if we're not yet on path
-        # s = (i * args.ocp_dt) * t_to_s
+        s = (i * args.ocp_dt) * t_to_s
         # full path
         # NOTE: this should be wrong, and ocp_dt correct,
         # but it works better for some reason xd
-        s = i * (max_s / args.n_knots)
+        # s = i * (max_s / args.n_knots)
         path2D.append(
             np.array(
                 [