diff --git a/examples/cart_pulling/path_following_from_planner.py b/examples/cart_pulling/path_following_from_planner.py
index 54581b9e40eaf4a228611c772d1bac20a3b1506e..8470014f5bbff713a4a54552c6eed5b33ef36788 100644
--- a/examples/cart_pulling/path_following_from_planner.py
+++ b/examples/cart_pulling/path_following_from_planner.py
@@ -32,8 +32,8 @@ def get_args():
 if __name__ == "__main__":
     args = get_args()
     robot = getRobotFromArgs(args)
-    robot._q[0] = 9.0
-    robot._q[1] = 4.0
+    #    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])
diff --git a/examples/log_analysis/log_analysis_example.py b/examples/log_analysis/log_analysis_example.py
index 4d92c970efc1da3b9dc3fdb05654d9cb957c813b..ea531f16c09e8f21aaabd02bae94ca18669bded9 100644
--- a/examples/log_analysis/log_analysis_example.py
+++ b/examples/log_analysis/log_analysis_example.py
@@ -1,16 +1,15 @@
 import pinocchio as pin
 import numpy as np
 import argparse
-from smc.util.logging_utils import LogManager
-from smc.visualize.manipulator_comparison_visualizer import (
+from smc.logging.logger import Logger
+from smc.visualization.manipulator_comparison_visualizer import (
     getLogComparisonArgs,
-    ManipulatorComparisonManager,
 )
 
 
 if __name__ == "__main__":
     args = getLogComparisonArgs()
-    log_manager = LogManager(None)
+    log_manager = Logger(None)
     log_manager.loadLog(args.log_file1)
     #    mcm = ManipulatorComparisonManager(args)
     #    mcm.visualizeWholeRuns()
diff --git a/python/smc/control/optimal_control/abstract_croco_ocp.py b/python/smc/control/optimal_control/abstract_croco_ocp.py
index fd68018311921e0e560de9a3d89a49e111a969a6..59a267fdc2839ca82ea83931f747ddfb9e0db901 100644
--- a/python/smc/control/optimal_control/abstract_croco_ocp.py
+++ b/python/smc/control/optimal_control/abstract_croco_ocp.py
@@ -105,10 +105,10 @@ class CrocoOCP(abc.ABC):
         # the first state is unlimited there idk what that means really,
         # but the arm's base isn't doing a full rotation anyway, let alone 2 or more
         self.xlb = np.concatenate(
-            [self.robot.model.lowerPositionLimit, -1 * self.robot.model.velocityLimit]
+            [self.robot.model.lowerPositionLimit, -1 * self.robot._max_v]
         )
         self.xub = np.concatenate(
-            [self.robot.model.upperPositionLimit, self.robot.model.velocityLimit]
+            [self.robot.model.upperPositionLimit, self.robot._max_v]
         )
 
         # NOTE: in an ideal universe this is handled elsewhere
@@ -135,6 +135,8 @@ 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 2ce182ff2d0830430f0514728d23e93c2d2b336e..1f1338c20ee3cfece648c6e9dcba1b91cfe8c273 100644
--- a/python/smc/control/optimal_control/croco_mpc_path_following.py
+++ b/python/smc/control/optimal_control/croco_mpc_path_following.py
@@ -1,4 +1,5 @@
 from smc.robots.interfaces.single_arm_interface import SingleArmInterface
+from smc.control.optimal_control.croco_mpc_point_to_point import CrocoIKMPC
 from smc.control.control_loop_manager import ControlLoopManager
 from smc.multiprocessing.process_manager import ProcessManager
 from smc.control.optimal_control.path_following_croco_ocp import (
@@ -16,6 +17,9 @@ import numpy as np
 from functools import partial
 import types
 import importlib.util
+from argparse import Namespace
+import pinocchio as pin
+import time
 
 if importlib.util.find_spec("mim_solvers"):
     try:
@@ -45,7 +49,7 @@ def CrocoEndEffectorPathFollowingMPCControlLoop(
     """
     breakFlag = False
     log_item = {}
-    save_past_dict = {}
+    save_past_item = {}
 
     q = robot.q
     T_w_e = robot.T_w_e
@@ -65,7 +69,7 @@ def CrocoEndEffectorPathFollowingMPCControlLoop(
             robot.sendVelocityCommand(np.zeros(robot.model.nv))
             log_item["qs"] = q.reshape((robot.model.nq,))
             log_item["dqs"] = robot.v.reshape((robot.model.nv,))
-            return breakFlag, save_past_dict, log_item
+            return breakFlag, save_past_item, log_item
 
         if data == "done":
             breakFlag = True
@@ -80,10 +84,11 @@ def CrocoEndEffectorPathFollowingMPCControlLoop(
 
         # 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:
-        # if t % 20 == 0:
         if t % int(np.ceil(args.ctrl_freq / 25)) == 0:
             robot.visualizer_manager.sendCommand({"frame_path": pathSE3})
 
@@ -115,19 +120,45 @@ def CrocoEndEffectorPathFollowingMPCControlLoop(
 
     log_item["qs"] = q.reshape((robot.model.nq,))
     log_item["dqs"] = robot.v.reshape((robot.model.nv,))
-    return breakFlag, save_past_dict, log_item
+    return breakFlag, save_past_item, log_item
 
 
-def CrocoEndEffectorPathFollowingMPC(args, robot, x0, path_planner):
+def CrocoEndEffectorPathFollowingMPC(
+    args: Namespace,
+    robot: SingleArmInterface,
+    x0: np.ndarray,
+    path_planner: ProcessManager,
+):
     """
     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).
+    follow a fixed pre-determined path, or a path received from a planner.
+    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()
 
@@ -143,9 +174,9 @@ def CrocoEndEffectorPathFollowingMPC(args, robot, x0, path_planner):
         CrocoEndEffectorPathFollowingMPCControlLoop, args, robot, solver, path_planner
     )
     log_item = {"qs": np.zeros(robot.model.nq), "dqs": np.zeros(robot.model.nv)}
-    save_past_dict = {}
+    save_past_item = {"T_w_e": robot.T_w_e}
     loop_manager = ControlLoopManager(
-        robot, controlLoop, args, save_past_dict, log_item
+        robot, controlLoop, args, save_past_item, log_item
     )
     loop_manager.run()
 
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 1f643a5b38837c0daf12a9d3c8914fd1365e9af2..30882035a5c396066e0825852da8d26be3522354 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
@@ -44,11 +44,12 @@ def CrocoIKMPCControlLoop(args, robot: SingleArmInterface, solver, T_w_goal, _,
     solver.solve(xs_init, us_init, args.max_solver_iter)
     xs = np.array(solver.xs)
     us = np.array(solver.us)
+    # NOTE: for some reason the first value is always some wild bs
     vel_cmds = xs[1, robot.model.nq :]
     robot.sendVelocityCommand(vel_cmds)
 
     log_item["qs"] = x0[: robot.model.nq]
-    log_item["dqs"] = x0[robot.model.nv :]
+    log_item["dqs"] = x0[robot.model.nq :]
     log_item["dqs_cmd"] = vel_cmds
     log_item["u_tau"] = us[0]
 
@@ -78,7 +79,7 @@ def CrocoIKMPC(args, robot, T_w_goal, run=True):
     controlLoop = partial(CrocoIKMPCControlLoop, args, robot, solver, T_w_goal)
     log_item = {
         "qs": np.zeros(robot.model.nq),
-        "dqs": np.zeros(robot.model.nq),
+        "dqs": np.zeros(robot.model.nv),
         "dqs_cmd": np.zeros(robot.model.nv),  # we're assuming full actuation here
         "u_tau": np.zeros(robot.model.nv),
     }
diff --git a/python/smc/robots/implementations/ur5e.py b/python/smc/robots/implementations/ur5e.py
index ba9768a545ac675a39896b902be43a411e64203c..dbbcb888cab1674ca19dee53148780db640752e2 100644
--- a/python/smc/robots/implementations/ur5e.py
+++ b/python/smc/robots/implementations/ur5e.py
@@ -40,6 +40,7 @@ class RobotManagerUR5e(ForceTorqueOnSingleArmWrist):
         if args.debug_prints:
             print("RobotManagerUR5e init")
         self._model, self._collision_model, self._visual_model, self._data = get_model()
+        self._ee_frame_id = self.model.getFrameId("tool0")
         self._MAX_ACCELERATION = 1.7  # const
         self._MAX_QD = 3.14  # const
         super().__init__(args)
diff --git a/python/smc/robots/interfaces/single_arm_interface.py b/python/smc/robots/interfaces/single_arm_interface.py
index c486f974f34f286ddfb5ffbcc5522a16bb268351..b6f6e46f15f8a23eca52b0a75b836b2ef4e55b0d 100644
--- a/python/smc/robots/interfaces/single_arm_interface.py
+++ b/python/smc/robots/interfaces/single_arm_interface.py
@@ -7,7 +7,7 @@ class SingleArmInterface(AbstractRobotManager):
     def __init__(self, args):
         if args.debug_prints:
             print("SingleArmInterface init")
-        self._ee_frame_id: int = self.model.getFrameId("tool0")
+        self._ee_frame_id: int
         self._T_w_e: pin.SE3
         super().__init__(args)
 
diff --git a/python/smc/robots/robotmanager_abstract.py b/python/smc/robots/robotmanager_abstract.py
index 0e6cee64fd793b776d2d509c94c7b95a7616c856..2b94efd52e249f42b0b7c02c6a673fb423f01c26 100644
--- a/python/smc/robots/robotmanager_abstract.py
+++ b/python/smc/robots/robotmanager_abstract.py
@@ -86,6 +86,8 @@ class AbstractRobotManager(abc.ABC):
         # TODO: each robot should know which grippers are available
         # and set this there.
         self.gripper = None
+        # initialize things that depend on q here
+        self._step()
 
         ####################################################################
         #                    processes and utilities robotmanager owns     #
diff --git a/python/smc/visualization/manipulator_comparison_visualizer.py b/python/smc/visualization/manipulator_comparison_visualizer.py
index 9c1785ca85807110a8e48f41de63e33c349a93d8..5c07d23e6d6b17897583c031948ad2b8a8ceb8e9 100644
--- a/python/smc/visualization/manipulator_comparison_visualizer.py
+++ b/python/smc/visualization/manipulator_comparison_visualizer.py
@@ -1,4 +1,4 @@
-from smc.robots.implementations.simulated_robot import SimulatedRobotManager
+from smc import getRobotFromArgs
 from smc.robots.utils import getMinimalArgParser
 from smc.multiprocessing.process_manager import ProcessManager
 from smc.logging.logger import Logger
@@ -40,8 +40,8 @@ class ManipulatorComparisonManager:
         args.pinocchio_only = True
         args.simulation = False
 
-        self.robot1 = SimulatedRobotManager(args)
-        self.robot2 = SimulatedRobotManager(args)
+        self.robot1 = getRobotFromArgs(args)
+        self.robot2 = getRobotFromArgs(args)
 
         # no two loops will have the same amount of timesteps.
         # we need to store the last available step for both robots.
@@ -49,13 +49,13 @@ class ManipulatorComparisonManager:
         self.lastq2 = np.zeros(self.robot2.model.nq)
 
         if os.path.exists(args.log_file1):
-            self.logm1 = LogManager(None)
+            self.logm1 = Logger(None)
             self.logm1.loadLog(args.log_file1)
         else:
             print("you did not give me a valid path for log1, exiting")
             exit()
         if os.path.exists(args.log_file2):
-            self.logm2 = LogManager(None)
+            self.logm2 = Logger(None)
             self.logm2.loadLog(args.log_file2)
         else:
             print("you did not give me a valid path for log2, exiting")