diff --git a/python/ur_simple_control/visualize/__pycache__/visualize.cpython-312.pyc b/python/ur_simple_control/visualize/__pycache__/visualize.cpython-312.pyc
index 8fcb405e56bc736d66c541ba4caf5b5ff6f02c36..74ae94bdf5b0861593f0609b21e017bba3c7be97 100644
Binary files a/python/ur_simple_control/visualize/__pycache__/visualize.cpython-312.pyc and b/python/ur_simple_control/visualize/__pycache__/visualize.cpython-312.pyc differ
diff --git a/python/ur_simple_control/visualize/manipulator_comparison_visualizer.py b/python/ur_simple_control/visualize/manipulator_comparison_visualizer.py
index 3dd8012350d72e63bb632eeebc78f9c33e8aecea..6682326d87da63d4aff4d17667a11075916bab25 100644
--- a/python/ur_simple_control/visualize/manipulator_comparison_visualizer.py
+++ b/python/ur_simple_control/visualize/manipulator_comparison_visualizer.py
@@ -6,7 +6,7 @@ from itertools import zip_longest
 from multiprocessing import Process, Queue
 from ur_simple_control.managers import getMinimalArgParser, RobotManager
 from ur_simple_control.util.logging_utils import LogManager
-from ur_simple_control.visualize.visualize import manipulatorComparisonVisualizer
+from ur_simple_control.visualize.visualize import manipulatorComparisonVisualizer, logPlotter
 
 def getLogComparisonArgs():
     parser = getMinimalArgParser()
@@ -53,40 +53,81 @@ class ManipulatorComparisonManager:
             print("you did not give me a valid path, exiting")
             exit()
 
-        self.manipulator_visualizer_queue = Queue()
+        self.manipulator_visualizer_cmd_queue = Queue()
+        self.manipulator_visualizer_ack_queue = Queue()
 
         # we are assuming both robots are the same,
         # but this does not necessarily have to be true
         self.manipulator_visualizer_process = Process(target=manipulatorComparisonVisualizer, 
                                                  args=(args, self.robot1.model, self.robot1.collision_model, 
-                                                       self.robot2.visual_model, self.manipulator_visualizer_queue, ))
+                                                       self.robot2.visual_model, self.manipulator_visualizer_cmd_queue, 
+                                                       self.manipulator_visualizer_ack_queue))
         if self.args.debug_prints:
             print("MANIPULATOR_COMPARISON_VISUALIZER: manipulator_comparison_visualizer_process started")
         self.manipulator_visualizer_process.start()
         # wait for meshcat to start
-        self.manipulator_visualizer_queue.put((np.zeros(self.robot1.model.nq), np.ones(self.robot2.model.nq)))
+        self.manipulator_visualizer_cmd_queue.put((np.zeros(self.robot1.model.nq), np.ones(self.robot2.model.nq)))
         if self.args.debug_prints:
             print("COMPARE_LOGS_MAIN: i managed to put initializing (q1, q2) to manipulator_comparison_visualizer_queue")
         # wait until it's ready (otherwise you miss half the sim potentially)
         # 5 seconds should be more than enough,
         # and i don't want to complicate this code by complicating IPC
         time.sleep(5)
+        self.manipulator_visualizer_ack_queue.get()
+
+        ###########################################
+        #  in case you will want log plotter too  #
+        ###########################################
+        self.log_plotter = False
+        self.log_plotter_cmd_queue = None
+        self.log_plotter_ack_queue = None
+        self.log_plotter_process = None
+
+
+    # NOTE i assume what you want to plot is a time-indexed with
+    # the same frequency that the control loops run in.
+    # if it's not then it's pointless to have a running plot anyway.
+    def createRunningPlot(self, log, log_plotter_time_start, log_plotter_time_stop):
+        self.log_plotter = True
+        self.log_plotter_time_start = log_plotter_time_start
+        self.log_plotter_time_stop = log_plotter_time_stop
+        self.log_plotter_cmd_queue = Queue()
+        self.log_plotter_ack_queue = Queue()
+        self.log_plotter_process = Process(target=logPlotter, 
+                                                 args=(args, log, self.log_plotter_cmd_queue, 
+                                                       self.log_plotter_ack_queue))
+        self.log_plotter_process.start()
+        self.log_plotter_ack_queue.get()
+
+    def updateViz(self, q1, q2, time_index):
+        self.manipulator_visualizer_cmd_queue.put((q1, q2))
+        if self.log_plotter and (time_index >= self.log_plotter_time_start) and (time_index < self.log_plotter_time_stop):
+            self.log_plotter_cmd_queue.put(time_index - self.log_plotter_time_start)
+            self.log_plotter_ack_queue.get()
+        self.manipulator_visualizer_ack_queue.get()
 
     # NOTE: this uses slightly fancy python to make it bareable to code
     # NOTE: dict keys are GUARANTEED to be in insert order from python 3.7 onward
-    def visualizeManipulatorRuns(self):
+    def visualizeWholeRuns(self):
+        time_index = 0 
         for control_loop1, control_loop2 in zip_longest(self.logm1.loop_logs, self.logm2.loop_logs):
             print(f'run {self.logm1.args.run_name}, controller: {control_loop1}')
             print(f'run {self.logm2.args.run_name}, controller: {control_loop2}')
             # relying on python's default thing.toBool()
             if not control_loop1:
+                print(f"run {self.logm1.args.run_name} is finished")
                 q1 = self.lastq1
                 for q2 in self.logm2.loop_logs[control_loop2]['qs']:
-                    self.manipulator_visualizer_queue.put_nowait((q1, q2))
+                    self.updateViz(q1, q2, time_index)
+                    time_index += 1
+                print(f"run {self.logm2.args.run_name} is finished")
             if not control_loop2:
+                print(f"run {self.logm2.args.run_name} is finished")
                 q2 = self.lastq2
                 for q1 in self.logm1.loop_logs[control_loop1]['qs']:
-                    self.manipulator_visualizer_queue.put_nowait((q1, q2))
+                    self.updateViz(q1, q2, time_index)
+                    time_index += 1
+                print(f"run {self.logm1.args.run_name} is finished")
             if control_loop1 and control_loop2:
                 for q1, q2 in zip_longest(self.logm1.loop_logs[control_loop1]['qs'], \
                             self.logm2.loop_logs[control_loop2]['qs']):
@@ -94,20 +135,21 @@ class ManipulatorComparisonManager:
                         self.lastq1 = q1
                     if not (q2 is None):
                         self.lastq2 = q2
-                    print(self.lastq1)
-                    print(self.lastq2)
-                    self.manipulator_visualizer_queue.put_nowait((self.lastq1, self.lastq2))
+                    self.updateViz(self.lastq1, self.lastq2, time_index)
+                    time_index += 1
 
 
 
 if __name__ == "__main__":
     args = getLogComparisonArgs()
-    visualizer = ManipulatorComparisonManager(args)
-    visualizer.visualizeManipulatorRuns()
+    cmp_manager = ManipulatorComparisonManager(args)
+    log_plot = {'random_noise' : np.random.normal(size=(1000, 2))}
+    cmp_manager.createRunningPlot(log_plot, 200, 1200)
+    cmp_manager.visualizeWholeRuns()
     time.sleep(100)
-    visualizer.manipulator_visualizer_queue.put("befree")
+    cmp_manager.manipulator_visualizer_cmd_queue.put("befree")
     print("main done")
     time.sleep(0.1)
-    visualizer.manipulator_visualizer_process.terminate()
+    cmp_manager.manipulator_visualizer_process.terminate()
     if args.debug_prints:
         print("terminated manipulator_visualizer_process")
diff --git a/python/ur_simple_control/visualize/visualize.py b/python/ur_simple_control/visualize/visualize.py
index 93f4a1347b63b71d99f7f3fc85156c4ff7dd01ea..9890be02c686b6795c4ef457de2f777797ec3314 100644
--- a/python/ur_simple_control/visualize/visualize.py
+++ b/python/ur_simple_control/visualize/visualize.py
@@ -58,16 +58,16 @@ def plotFromDict(plot_data, final_iteration, args):
     plt.show()
 
 
-"""
-realTimePlotter
----------------
-- true to its name
-"""
 # STUPID MATPLOTLIB CAN'T HANDLE MULTIPLE FIGURES FROM DIFFERENT PROCESS
 # LITERALLY REFUSES TO GET ME A FIGURE
 def realTimePlotter(args, queue):
+    """
+    realTimePlotter
+    ---------------
+    - true to its name
+    - plots whatever you are logging if you use the --real-time-plotting flag
+    """
     if args.debug_prints:
-#        print("REAL_TIME_PLOTTER: real time visualizer has been summoned")
         print("REAL_TIME_PLOTTER: i got this queue:", queue)
     plt.ion()
     fig = plt.figure()
@@ -203,7 +203,7 @@ def manipulatorVisualizer(args, model, collision_model, visual_model, queue):
 # could be merged with the above function.
 # but they're different enough in usage to warrent a different function,
 # instead of polluting the above one with ifs
-def manipulatorComparisonVisualizer(args, model, collision_model, visual_model, queue):
+def manipulatorComparisonVisualizer(args, model, collision_model, visual_model, cmd_queue, ack_queue):
     # for whatever reason the hand-e files don't have/
     # meshcat can't read scaling information.
     # so we scale manually
@@ -213,6 +213,9 @@ def manipulatorComparisonVisualizer(args, model, collision_model, visual_model,
             # this looks exactly correct lmao
             s *= 0.001
             geom.meshScale = s
+            # there has to be a way
+            # if not here than elsewhere
+            geom.meshColor = np.array([0.2,0.2,0.2,0.2])
     for geom in collision_model.geometryObjects:
         if "hand" in geom.name:
             s = geom.meshScale
@@ -222,24 +225,59 @@ def manipulatorComparisonVisualizer(args, model, collision_model, visual_model,
     viz = MeshcatVisualizer(model, collision_model, visual_model)
     viz.initViewer(open=True)
     # load the first one
-    viz.loadViewerModel()
+    viz.loadViewerModel(collision_color=[0.2,0.2,0.2,0.6])
+    #viz.viewer["pinocchio/visuals"].set_property("visible", False)
+    #viz.viewer["pinocchio/collisions"].set_property("visible", True)
+    viz.displayVisuals(False)
+    viz.displayCollisions(True)
     # maybe needed
     #viz.displayVisuals(True)
+#    meshpath = viz.viewerVisualGroupName 
+#    for geom in visual_model(6):
+#        meshpath_i = meshpath + "/" + geometry_object.name
+    #viz.viewer["pinocchio/visuals"].set_property("opacity", 0.2)
+    #viz.viewer["pinocchio"].set_property("opacity", 0.2)
+    #viz.viewer["pinocchio/visuals/forearm_link_0"].set_property("opacity", 0.2)
+
+    #viz.viewer["pinocchio"].set_property("color", (0.2,0.2,0.2,0.2))
+    #viz.viewer["pinocchio/visuals"].set_property("color",(0.2,0.2,0.2,0.2))
+    #viz.viewer["pinocchio/visuals/forearm_link_0"].set_property("color", (0.2,0.2,0.2,0.2))
+
+    ## this is the path we want, with the /<object> at the end
+    #node = viz.viewer["pinocchio/visuals/forearm_link_0/<object>"]
+    #print(node)
+    #node.set_property("opacity", 0.2)
+    #node.set_property("modulated_opacity", 0.2)
+    #node.set_property("color", [0.2] * 4)
+    #node.set_property("scale", 100.2)
+    # this one actually works
+    #node.set_property("visible", False)
+
+    node = viz.viewer["pinocchio/visuals"]
+    #node.set_property("visible", False)
+    node.set_property("modulated_opacity", 0.4)
+    node.set_property("opacity", 0.2)
+    node.set_property("color", [0.2] * 4)
+    
+    #meshcat->SetProperty("path/to/my/thing/<object>", "opacity", alpha);
+
 
     # other robot display
     viz2 = MeshcatVisualizer(model, collision_model, visual_model)
     viz2.initViewer(viz.viewer)
     # i don't know if rootNodeName does anything apart from being different
+    #viz2.loadViewerModel(rootNodeName="pinocchio2", visual_color=(1.0,1.0,1.0,0.1))
     viz2.loadViewerModel(rootNodeName="pinocchio2")
     # initialize
-    q1, q2 = queue.get()
+    q1, q2 = cmd_queue.get()
     viz.display(q1)
     viz2.display(q2)
 
+    ack_queue.put("ready")
     print("MANIPULATOR_COMPARISON_VISUALIZER: FULLY ONLINE")
     try:
         while True:
-            q = queue.get()
+            q = cmd_queue.get()
             if type(q) == str:
                 print("got str q")
                 if q == "befree":
@@ -251,9 +289,93 @@ def manipulatorComparisonVisualizer(args, model, collision_model, visual_model,
             q1, q2 = q
             viz.display(q1)
             viz2.display(q2)
+            # this doesn't really work because meshcat is it's own server
+            # and display commands just relay their command and return immediatelly.
+            # but it's better than nothing. 
+            # NOTE: if there's lag in meshcat, just add a small sleep here before the 
+            # ack signal - that will ensure synchronization because meshat will actually be ready
+            ack_queue.put("ready")
     except KeyboardInterrupt:
         if args.debug_prints:
             print("MANIPULATOR_COMPARISON_VISUALIZER: caught KeyboardInterrupt, i'm out")
         viz.viewer.window.server_proc.kill()
         viz.viewer.window.server_proc.wait()
 
+
+
+def logPlotter(args, log, cmd_queue, ack_queue):
+    """
+    logPlotter
+    ---------------
+    - plots whatever you want as long as you pass the data
+      as a dictionary where the key will be the name on the plot, 
+      and the value have to be the dependent variables - 
+      the independent variable is time and has to be the same for all items.
+      if you want to plot something else, you need to write your own function.
+      use this as a skeleton if you want that new plot to be updating too
+      as then you don't need to think about IPC - just use what's here.
+    - this might be shoved into a tkinter gui if i decide i really need buttons
+    """
+    if len(log) == 0:
+        print("you've send me nothing, so no real-time plotting for you")
+        return
+
+    plt.ion()
+    fig = plt.figure()
+    canvas = fig.canvas
+    AxisAndArtists = namedtuple("AxAndArtists", "ax artists")
+    axes_and_updating_artists = {}
+
+    n_cols, n_rows = getNRowsMColumnsFromTotalNumber(len(log))
+    # this is what subplot wants
+    subplot_col_row = str(n_cols) + str(n_rows)
+    # preload some zeros and initialize plots
+    for i, data_key in enumerate(log):
+        # you give single-vector numpy arrays, i instantiate and plot lists of these 
+        # so for your (6,) vector, i plot (N, 6) ndarrays resulting in 6 lines of length N.
+        # i manage N because plot data =/= all data for efficiency reasons.
+        assert type(log[data_key]) == np.ndarray
+
+        colors = plt.cm.jet(np.linspace(0, 1, log[data_key].shape[1]))
+        ax = fig.add_subplot(int(subplot_col_row + str(i + 1)))
+        ax.set_title(data_key)
+        # we plot each line separately so that they have different colors
+        # we assume (N_timesteps, your_vector) shapes.
+        # values do not update
+        for j in range(log[data_key].shape[1]):
+            # NOTE the same length assumption plays a part for correctness,
+            # but i don't want that to be an error in case you know what you're doing
+            ax.plot(np.arange(len(log[data_key])), log[data_key][:,j], 
+                        color=colors[j], label=data_key + "_" + str(j))
+
+        # vertical bar does update
+        point_in_time_line = ax.axvline(x=0, color='red', animated=True)
+        axes_and_updating_artists[data_key] = AxisAndArtists(ax, point_in_time_line)
+        axes_and_updating_artists[data_key].ax.legend(loc='upper left')
+
+    # need to call it once
+    canvas.draw()
+    canvas.flush_events()
+    background = canvas.copy_from_bbox(fig.bbox)
+
+    ack_queue.put("ready")
+    if args.debug_prints:
+        print("LOG_PLOTTER: FULLY ONLINE")
+    try:
+        while True:
+            time_index = cmd_queue.get()
+            if time_index == "befree":
+                if args.debug_prints:
+                    print("LOG_PLOTTER: got befree, logPlotter out")
+                break
+            canvas.restore_region(background)
+            for data_key in log:
+                axes_and_updating_artists[data_key].artists.set_xdata([time_index])
+                axes_and_updating_artists[data_key].ax.draw_artist(axes_and_updating_artists[data_key].artists)
+            canvas.blit(fig.bbox)
+            canvas.flush_events()
+            ack_queue.put("ready")
+    except KeyboardInterrupt:
+        if args.debug_prints:
+            print("LOG_PLOTTER: caught KeyboardInterrupt, i'm out")
+    plt.close(fig)