diff --git a/python/examples/.drawing_from_input_drawing.py.swp b/python/examples/.drawing_from_input_drawing.py.swp
index cb6dfeb6f1e8d357a11c2c00ba7b286da2049046..424abe01a262c1f14f8d73501a3dc4a74581419a 100644
Binary files a/python/examples/.drawing_from_input_drawing.py.swp and b/python/examples/.drawing_from_input_drawing.py.swp differ
diff --git a/python/examples/drawing_from_input_drawing.py b/python/examples/drawing_from_input_drawing.py
index 14e04c89b2a5debe9193adf70e6bb001950289de..89d1dabc162841c8e9ecdd9ebe3f013f28209ae1 100644
--- a/python/examples/drawing_from_input_drawing.py
+++ b/python/examples/drawing_from_input_drawing.py
@@ -38,6 +38,9 @@ from ur_simple_control.clik.clik_point_to_point import getClikController
 #######################################################################
 # TODO sort these somehow
 def get_args():
+    #######################################################################
+    #                          generic arguments                          #
+    #######################################################################
     parser = argparse.ArgumentParser(description='Make a drawing on screen,
             watch the robot do it on the whiteboard.',
             formatter_class=argparse.ArgumentDefaultsHelpFormatter)
@@ -55,23 +58,28 @@ def get_args():
                     NOTE: not implemented yet", default=False)
     parser.add_argument('--gripper', action=argparse.BooleanOptionalAction, \
             help="whether you're using the gripper", default=True)
-# not applicable here, but leaving it in the case it becomes applicable
-# it's also in the robot manager even though it shouldn't be
-    parser.add_argument('--goal-error', type=float, \
-            help="the final position error you are happy with. NOTE: not used here", \
-            default=1e-3)
-    parser.add_argument('--max-iterations', type=int, \
-            help="maximum allowable iteration number (it runs at 500Hz)", default=100000)
     parser.add_argument('--acceleration', type=float, \
             help="robot's joints acceleration. scalar positive constant, \
             max 1.7, and default 0.4. \
             BE CAREFUL WITH THIS. the urscript doc says this is 'lead axis acceleration'.\
             TODO: check what this means", default=0.3)
-    # TODO: test the interaction of this and the overall demo
     parser.add_argument('--speed-slider', type=float,\
             help="cap robot's speed with the speed slider \
                     to something between 0 and 1, 0.5 by default \
                     BE CAREFUL WITH THIS.", default=0.5)
+    parser.add_argument('--max-iterations', type=int, \
+            help="maximum allowable iteration number (it runs at 500Hz)", default=100000)
+    #######################################################################
+    #                 your controller specific arguments                  #
+    #######################################################################
+    # not applicable here, but leaving it in the case it becomes applicable
+    # it's also in the robot manager even though it shouldn't be
+    parser.add_argument('--past-window-size', type=int, \
+            help="how many timesteps of past data you want to save", default=5)
+    parser.add_argument('--goal-error', type=float, \
+            help="the final position error you are happy with. NOTE: not used here", \
+            default=1e-3)
+    # TODO: test the interaction of this and the overall demo
     parser.add_argument('--tikhonov-damp', type=float, \
             help="damping scalar in tiknohov regularization.\
             This is used when generating the joint trajectory from the drawing.", \
@@ -92,9 +100,6 @@ def get_args():
     parser.add_argument('--kp', type=float, \
             help="proportial control constant for position errors", \
             default=2.0)
-    parser.add_argument('--kp', type=float, \
-            help="proportial control constant for position errors", \
-            default=2)
     parser.add_argument('--tau0', type=float, \
             help="total time needed for trajectory. if you use temporal coupling,\
                   you can still follow the path even if it's too fast", \
@@ -128,77 +133,82 @@ def get_args():
 def controller():
     pass
 
-def controlLoopWriting(robot, dmp, controller, i):
-    # dmp
-    dmp.step(robot.dt)
-    # temporal coupling
+# TODO:
+# regarding saving data you have 2 options:
+# 1) explicitely return what you want to save - you can't magically read local variables
+# 2) make controlLoop a class and then save handle the saving behind the scenes -
+#    now you these variables are saved in a class so they're not local variables
+# option 1 is clearly more hands-on and thus worse
+# option 2 introduces a third big class and makes everything convoluted.
+# for now, we go for option 1) because it's simpler to implement and deal with.
+# but in the future, implementing 2) should be tried. you really have to try 
+# to do it cleanly to see how good/bad it is.
+# in that case you'd most likely want to inherit ControlLoopManager and go from there.
+# you'd still need to specify what you're saving with self.that_variable so no matter
+# there's no running away from that in any case.
+# it's 1) for now, it's the only non-idealy-clean part of this solution, and it's ok really.
+
+# control loop to be passed to ControlLoopManager
+def controlLoopWriting(dmp, tc, controller, robot, i, past_data):
+    breakFlag = False
+    # TODO rename this into something less confusing
+    save_past_dict = {}
+    log_dict = {}
+    dmp.step(robot.dt) # dmp step
+    # temporal coupling step
     tau = dmp.tau + tc.update(dmp, robot.dt) * robot.dt
     dmp.set_tau(tau)
     q = robot.getQ()
-    # TODO: document the mathematics with one-line comments
-    wrench = robot.getWrench()
-    # rolling average
-    # TODO: try doing a low-pass filter instead
-    # TODO handle past data in some nice way from here or elsewhere?
-    wrench_avg[i % 5] = wrench
-    wrench = np.average(wrench_avg, axis=0)
-    # pinocchio model is 8-dof because it treats the gripper
-    # as two prismatic joints
-    pin.forwardKinematics(model, data, q)
-    # calculate joint torques based on the force-torque sensor
     # TODO look into UR code/api for estimating the same
     # based on currents in the joints.
     # it's probably worse, but maybe some sensor fusion-type thing
-    # is actually better, who knows
-    J = pin.computeJointJacobian(model, data, q, JOINT_ID)
-    dq = np.array(rtde_receive.getActualQd()).reshape((6,1))
+    # is actually better, who knows.
+    # also you probably want to do the fusion of that onto tau (got from J.T @ wrench)
+    wrench = robot.getWrench()
+    # deepcopy for good coding practise (and correctness here)
+    save_past_dict['wrench'] = copy.deepcopy(wrench)
+    # rolling average
+    # TODO: try doing a low-pass filter instead
+    # TODO: check where the shape of this is correct (should be, but check)
+    wrench = np.average(np.array(past_data['wrench']), axis=0)
+    pin.forwardKinematics(robot.model, robot.data, q)
+    J = pin.computeJointJacobian(robot.model, robot.data, q, robot.JOINT_ID)
+    dq = robot.getQd().reshape((6,1))
+    # get joitn 
     tau = J.T @ wrench
     tau = tau[:6].reshape((6,1))
     # compute control law:
     # - feedforward the velocity and the force reading
     # - feedback the position 
+    # TODO: don't use vel for qd, it's confusion (yes, that means changing dmp code too)
+    # TODO: put this in a controller function for easy swapping (or don't if you won't swap)
     vel_cmd = dmp.vel + kp * (dmp.pos - q6.reshape((6,1))) - alpha * tau
-    vel_cmd = vel_cmd.reshape((6,))
-    # just in case, clip the velocity to max values
-    vel_cmd = np.clip(vel_cmd, -1 * v_max, v_max)
-    # and immediatelly stop if something weird happened (some non-convergence)
+    robot.sendQd(vel_cmd, acceleration, dt)
+
+    # TODO find a better criterion for stopping
+    if (np.linalg.norm(dmp.vel) < 0.0001) and (i > 5000):
+        breakFlag = True
+    # immediatelly stop if something weird happened (some non-convergence)
     if np.isnan(vel_cmd[0]):
-        break
-    rtde_control.speedJ(vel_cmd, acceleration, dt)
+        breakFlag = True
 
-    # save values for the plot
-    # TODO cut off at the last time instant?
-    qs[i] = q6.reshape((6,))
-    dmp_poss[i] = dmp.pos.reshape((6,))
-    dqs[i] = dq.reshape((6,))
-    dmp_vels[i] = dmp.vel.reshape((6,))
+    # log what you said you'd log
+    log_dict['qs'] = q6.reshape((6,))
+    log_dict['dmp_poss'] = dmp.pos.reshape((6,))
+    log_dict['dqs'] = dq.reshape((6,))
+    log_dict['dmp_vels'] = dmp.vel.reshape((6,))
 
-    # TODO do this with something sensible to stop
-    if (np.linalg.norm(dmp.vel) < 0.0001) and (i > 5000):
-        break
+    return breakFlag, save_past_dict, log_dict
 
 if __name__ == "__main__":
+    #######################################################################
+    #                           software setup                            #
+    #######################################################################
     args = parser.get_args()
     clik_controller = getController(args)
     robot = RobotManager(args)
-    plot_dict = {
-        'qs' : np.zeros((args.max_iterations, 6)),
-        'dmp_poss' : np.zeros((args.max_iterations, 6)),
-        'dqs' : np.zeros((args.max_iterations, 6)),
-        'dmp_vels' : np.zeros((args.max_iterations, 6)),
-    }
-
     # load trajectory, create DMP based on it
     dmp = DMP()
-    # TODO: add marker picking
-    # get up from the board
-    current_pose = rtde_receive.getActualTCPPose()
-    current_pose[2] = current_pose[2] + 0.03
-    rtde_control.moveL(current_pose)
-    # move to initial pose
-    # this is a blocking call 
-    rtde_control.moveJ(dmp.pos.reshape((6,)))
-
     if not args.temporal_coupling:
         tc = NoTC()
     else:
@@ -216,3 +226,30 @@ if __name__ == "__main__":
     # ALSO NOTE: to use this you need to change the version inclusions in
     # ur_rtde due to a bug there in the current ur_rtde + robot firmware version 
     # (the bug is it works with the firmware verion, but ur_rtde thinks it doesn't)
+    # here you give what you're saving in the rolling past window 
+    # it's initial value.
+    # controlLoopManager will populate the queue with these initial values
+    save_past_dict = {
+            'wrench' : np.zeros(6),
+        }
+    # here you give it it's initial value
+    log_dict = {
+            'qs' : np.zeros((args.max_iterations, 6)),
+            'dmp_poss' : np.zeros((args.max_iterations, 6)),
+            'dqs' : np.zeros((args.max_iterations, 6)),
+            'dmp_vels' : np.zeros((args.max_iterations, 6)),
+        }
+    controlLoop = partial(controlLoopWriting, dmp, tc, controller, robot)
+    loop_manager = ControlLoopManager(robot, controlLoop, args, save_past_dict, log_dict)
+    #######################################################################
+    #                           physical setup                            #
+    #######################################################################
+    # TODO: add marker picking
+    # get up from the board
+    current_pose = rtde_receive.getActualTCPPose()
+    current_pose[2] = current_pose[2] + 0.03
+    rtde_control.moveL(current_pose)
+    # move to initial pose
+    # this is a blocking call 
+    rtde_control.moveJ(dmp.pos.reshape((6,)))
+
diff --git a/python/ur_simple_control/.managers.py.swp b/python/ur_simple_control/.managers.py.swp
index a43d7d58d3891f18e11ae16742e2ed16edc12893..0b0b860270552b6cb3f46e23f92ce3b1971f0e75 100644
Binary files a/python/ur_simple_control/.managers.py.swp and b/python/ur_simple_control/.managers.py.swp differ
diff --git a/python/ur_simple_control/clik/.clik_point_to_point.py.swp b/python/ur_simple_control/clik/.clik_point_to_point.py.swp
index 57c33bd5da6b047cba444428a0e241de82cfc793..864b03fab06bf205414a942d9890f8385c8f24fb 100644
Binary files a/python/ur_simple_control/clik/.clik_point_to_point.py.swp and b/python/ur_simple_control/clik/.clik_point_to_point.py.swp differ
diff --git a/python/ur_simple_control/clik/clik_point_to_point.py b/python/ur_simple_control/clik/clik_point_to_point.py
index 54614006e1a9cffd01466c1c0095577a98815eae..c98b050fe66b37ed344176c30b5cff8a439ca421 100644
--- a/python/ur_simple_control/clik/clik_point_to_point.py
+++ b/python/ur_simple_control/clik/clik_point_to_point.py
@@ -104,6 +104,7 @@ def getClikController(args):
 # modularity yo
 def controlLoopClik(robot, clik_controller, i):
     breakFlag = False
+    save_past_dict = {}
     # know where you are, i.e. do forward kinematics
     q = robot.getQ()
     pin.forwardKinematics(robot.model, robot.data, q)
@@ -126,7 +127,9 @@ def controlLoopClik(robot, clik_controller, i):
         print("linear error = ", pin.log6(SEerror).linear)
         print("angular error = ", pin.log6(SEerror).angular)
         print(" error = ", err_vector.transpose())
-    return breakFlag
+    # we're not saving or loggin here, but need to respect the API, 
+    # hence the empty dicts
+    return breakFlag, {}, {}
 
 
 
@@ -136,5 +139,6 @@ if __name__ == "__main__":
     Mgoal = robot.defineGoalPoint()
     clik_controller = getClikController(args)
     controlLoop = partial(controlLoopClik, robot, clik_controller)
-    loop_manager = ControlLoopManager(robot, controlLoop, args)
+    # we're not using any past data or logging, hence the empty arguments
+    loop_manager = ControlLoopManager(robot, controlLoop, args, {}, {})
     loop_manager.run()
diff --git a/python/ur_simple_control/managers.py b/python/ur_simple_control/managers.py
index e9b7f5882d22cb4d1433f049926ff7f411436e1b..3afdb8111aaf6403249e098b4c57f84af3f70da2 100644
--- a/python/ur_simple_control/managers.py
+++ b/python/ur_simple_control/managers.py
@@ -1,4 +1,5 @@
 # TODO clear out unnecessary imports
+# TODO rename all private variables to start with '_'
 import pinocchio as pin
 import numpy as np
 import time
@@ -14,6 +15,7 @@ import signal
 from ur_simple_control.util.get_model import get_model
 from ur_simple_control.util.robotiq_gripper import RobotiqGripper
 import argparse
+from collections import deque
 
 """
 general notes
@@ -50,7 +52,8 @@ and create child classes for particular robots.
 """
 ControlLoopManager
 -------------------
-Slightly fancier programming to get a wrapper around the control loop.
+Slightly fancier programming (passing a function as argument and using functools.partial)
+to get a wrapper around the control loop.
 In other words, it's the book-keeping around the actual control loop.
 It's a class because it keeps non-directly-control-loop-related parameters
 like max_iterations, what data to save etc.
@@ -58,7 +61,7 @@ NOTE: you give this the ready-made control loop.
 if it has arguments, bake them in with functools.partial.
 """
 class ControlLoopManager:
-    def __init__(self, robot_manager, controlLoop, args):
+    def __init__(self, robot_manager, controlLoop, args, save_past_dict, log_dict):
         self.max_iterations = args.max_iterations
         # i need rtde_control do to rtde_control
         self.robot_manager = robot_manager
@@ -67,12 +70,6 @@ class ControlLoopManager:
         # predefined ur magic numbers
         self.update_rate = 500
         self.dt = 1 / self.update_rate
-
-        # if you just stop it the program with Ctrl-C, it will continue running
-        # the last speedj lmao.
-        # there's also an actual stop command, but sending 0s works so i'm not changing it
-        signal.signal(signal.SIGINT, self.stopHandler)
-
         # TODO handle data saving here.
         # it can only be handled here because the control loop only cares about the present/
         # a small time-window around it.
@@ -82,6 +79,39 @@ class ControlLoopManager:
         # if they're not consider inheriting and specializing this or sth similar,
         # we'll see when we get to it
 
+        # it's a dictionaries of deque because deque is the most convenient class to have for removing the first 
+        # element and appending a last (it is just a linked list under the hood of course).
+        # it's a dictionary for modularity's sake, because this way you can save whatever you want
+        # and it will just work based on dictionary keys.
+        # it is the user's resposibility to make sure they're providing correct data.
+        # --> but make an assert for the keys at least
+        # in the c++ imlementation, make the user write their own struct or something.
+        # TODO: figure out how to populate it initially
+        # you have the length from the arguments, but you also need the keys.
+        # since those won't be changed, it doesn't make sense to have them as program arguments.
+        # --> make them arguments of this class, there is no better solution
+        self.past_data = {}
+        # save_past_dict has to have the key and 1 example of what you're saving
+        # so that it's type can be inferred (but we're in python so types don't really work).
+        # the good thing is this way you also immediatelly put in the initial values
+        for key in save_past_dict:
+            self.past_data[key] = deque()
+            # immediatelly populate every deque with initial values
+            for i in range(args.past_window_size):
+                # deepcopy just in case, better safe than sorry. 
+                # and we can be slow with initialization.
+                self.past_data[key].append(copy.deepcopy(save_past_dict[key]))
+
+        # TODO save and handle plotting data in the exact same fashion.
+        # also write out docs on what's available (and then assert what's defined
+        # to have to be in the list of available things)
+
+        # if you just stop it the program with Ctrl-C, it will continue running
+        # the last speedj lmao.
+        # there's also an actual stop command, but sending 0s works so i'm not changing it
+        signal.signal(signal.SIGINT, self.stopHandler)
+
+
     """
     stopHandler
     -----------
@@ -109,7 +139,19 @@ class ControlLoopManager:
     def run(self):
         for i in range(self.max_iterations):
             start = time.time()
-            breakFlag = self.controlLoop(i)
+            breakFlag, latest_to_save_dict, log_entry_dict = self.controlLoop(i, self.past_data)
+            # update past rolling window
+            # TODO: write an assert assuring the keys are what's been promised
+            # (ideally this is done only once, not every time, so think whether/how that can be avoided)
+            for key in latest_to_save_dict:
+                # remove oldes entry
+                self.past_data[key].popleft()
+                # add new entry
+                self.past_data[key].append(latest_to_save_dict[key])
+            
+            # log the data
+
+            # break if done
             if breakFlag:
                 break
             end = time.time()
@@ -169,6 +211,16 @@ class RobotManager:
             #viz.display(q0)
         if self.gripper_flag and not self.pinocchio_only:
             self.gripper = RobotiqGripper()
+        # TODO: this is obviously at least a partial redundancy w.r.t. the pinocchio data object
+        #        however i have no time to investigate what i get or don't get there
+        #        so i'll just leave this be. but fix this!!!! we don't want to duplicate information!!
+        # also TODO: figure out how to best solve the gripper_velocity problem
+        #self.q = np.zeros(6)
+        #self.qd = np.zeros(6)
+        #self.qdd = np.zeros(6)
+        #self.gripper_pos = 0.0
+        #self.gripper_past_pos = 0.0
+        # set q,qd,qdd,griper_pos, gripper_past_pos, gripper_vel to dummy values
         # initialize and connect the interfaces
         self.simulation = args.simulation
         if self.pinocchio_only:
@@ -206,7 +258,6 @@ class RobotManager:
         # TODO: these are almost certainly higher
         # maybe you want to have them high and cap the rest with speed slider?
         # idk really, but it's better to have this low and burried for safety and robot longevity reasons
-        # TODO: 
         self.max_qdd = 1.7
         # NOTE: i had this thing at 2 in other code
         self.max_qd = 0.5
@@ -221,15 +272,19 @@ class RobotManager:
     but they do not affect the overall movement
     of the robot, so we add or remove 2 items to the joint list.
     also, the gripper is controlled separately so we'd need to do this somehow anyway 
+    NOTE: this gripper_past_pos thing is not working atm, but i'll keep it here as a TODO
     """
     def getQ(self):
         if not self.pinocchio_only:
             q = self.rtde_receive.getActualQ()
             if self.args.gripper:
+                # TODO: make it work or remove it
+                #self.gripper_past_pos = self.gripper_pos
+                # this is pointless by itself
                 self.gripper_pos = gripper.get_current_position()
-                # the /255 is to get it dimensionless
-                # the gap is 5cm
-                # thus half the gap is 0.025m and we only do si units here
+                # the /255 is to get it dimensionless.
+                # the gap is 5cm,
+                # thus half the gap is 0.025m (and we only do si units here).
                 q.append((self.gripper_pos / 255) * 0.025)
                 q.append((self.gripper_pos / 255) * 0.025)
             else:
@@ -244,6 +299,41 @@ class RobotManager:
             return self.q
         return q
 
+    """
+    getQd
+    -----
+    same note as getQ.
+    TODO NOTE: atm there's no way to get current gripper velocity.
+    this means you'll probably want to read current positions and then finite-difference 
+    to get the velocity.
+    as it stands right now, we'll just pass zeros in because I don't need this ATM
+    """
+    def getQd(self):
+        if not self.pinocchio_only:
+            qd = self.rtde_receive.getActualQd()
+            if self.args.gripper:
+                # TODO: this doesn't work because we're not ensuring stuff is called 
+                # at every timestep
+                #self.gripper_vel = (gripper.get_current_position() - self.gripper_pos) / self.dt
+                # so it's just left unused for now - better give nothing than wrong info
+                self.gripper_vel = 0.0
+                # the /255 is to get it dimensionless
+                # the gap is 5cm
+                # thus half the gap is 0.025m and we only do si units here
+                q.append(self.gripper_vel)
+                q.append(self.gripper_vel)
+            else:
+                # just fill it with zeros otherwise
+                q.append(0.0)
+                q.append(0.0)
+        # let's just have both options for getting q, it's just a 8d float list
+        # readability is a somewhat subjective quality after all
+            qd = np.array(qd)
+            self.qd = qd
+        else:
+            return self.qd
+        return qd
+
     """
     getWrench
     -----
diff --git a/python/ur_simple_control/util/.robotiq_gripper.py.swp b/python/ur_simple_control/util/.robotiq_gripper.py.swp
new file mode 100644
index 0000000000000000000000000000000000000000..c8c21fa7784dcb063d1e16b0a39ee21db92da85c
Binary files /dev/null and b/python/ur_simple_control/util/.robotiq_gripper.py.swp differ