diff --git a/src/hub.html b/src/hub.html
index 436561df51208ba71a0061c8cde58a6efe8966bf..5af532ed669a0578a279f7b6edc84e3307b6bd01 100644
--- a/src/hub.html
+++ b/src/hub.html
@@ -352,7 +352,7 @@
                     button: gaze-teleport_;
                     collisionEntities: [nav-mesh];
                     drawIncrementally: true;
-                    incrementalDrawMs: 600;
+                    incrementalDrawMs: 300;
                     hitOpacity: 0.3;
                     missOpacity: 0.1;
                     curveShootingSpeed: 12;"
@@ -372,7 +372,7 @@
                   button: left-teleport_;
                   collisionEntities: [nav-mesh];
                   drawIncrementally: true;
-                  incrementalDrawMs: 600;
+                  incrementalDrawMs: 300;
                   hitOpacity: 0.3;
                   missOpacity: 0.1;
                   curveShootingSpeed: 12;"
@@ -398,7 +398,7 @@
                   button: right-teleport_;
                   collisionEntities: [nav-mesh];
                   drawIncrementally: true;
-                  incrementalDrawMs: 600;
+                  incrementalDrawMs: 300;
                   hitOpacity: 0.3;
                   missOpacity: 0.1;
                   curveShootingSpeed: 12;"
diff --git a/src/systems/userinput/bindings/oculus-touch-user.js b/src/systems/userinput/bindings/oculus-touch-user.js
index cf746a11e65647faeee97e51f0601b4612d39464..9f475b833472577d1a2447c3ab74a39072ff1797 100644
--- a/src/systems/userinput/bindings/oculus-touch-user.js
+++ b/src/systems/userinput/bindings/oculus-touch-user.js
@@ -21,6 +21,10 @@ const rightHandDrop2 = `${name}right/rightHandDrop2`;
 const rightHandDrop1 = `${name}right/rightHandDrop1`;
 const rightGripRising = `${name}right/GripRising`;
 const rightTriggerRising = `${name}right/TriggerRising`;
+const rightGripRisingGrab = `${name}right/grip/RisingGrab`;
+const rightTriggerRisingGrab = `${name}right/trigger/RisingGrab`;
+const leftGripRisingGrab = `${name}left/grip/RisingGrab`;
+const leftTriggerRisingGrab = `${name}left/trigger/RisingGrab`;
 const leftGripFalling = `${name}left/GripFalling`;
 const leftGripRising = `${name}left/GripRising`;
 const leftTriggerRising = `${name}left/TriggerRising`;
@@ -65,7 +69,7 @@ export const oculusTouchUserBindings = {
     {
       src: {
         x: leftAxis("joyX"),
-        y: leftAxis("joyV")
+        y: leftAxis("joyY")
       },
       dest: {
         value: leftJoy
@@ -286,17 +290,22 @@ export const oculusTouchUserBindings = {
   [sets.leftHandHoveringOnInteractable]: [
     {
       src: { value: leftButton("grip").pressed },
-      dest: { value: paths.actions.leftHand.grab },
+      dest: { value: leftGripRisingGrab },
       xform: xforms.rising,
       root: leftGripRising,
       priority: 200
     },
     {
       src: { value: leftButton("trigger").pressed },
-      dest: { value: paths.actions.leftHand.grab },
+      dest: { value: leftTriggerRisingGrab },
       xform: xforms.rising,
       root: leftTriggerRising,
       priority: 200
+    },
+    {
+      src: [leftGripRisingGrab, leftTriggerRisingGrab],
+      dest: { value: paths.actions.cursor.grab },
+      xform: xforms.any
     }
   ],
 
@@ -310,15 +319,7 @@ export const oculusTouchUserBindings = {
     }
   ],
 
-  [sets.leftHandHoveringOnPen]: [
-    {
-      src: { value: leftButton("trigger").pressed },
-      dest: { value: paths.actions.leftHand.grab },
-      xform: xforms.rising,
-      root: leftTriggerRising,
-      priority: 200
-    }
-  ],
+  [sets.leftHandHoveringOnPen]: [],
   [sets.leftHandHoldingPen]: [
     {
       src: { value: leftButton("trigger").pressed },
@@ -380,17 +381,22 @@ export const oculusTouchUserBindings = {
   [sets.cursorHoveringOnInteractable]: [
     {
       src: { value: rightButton("grip").pressed },
-      dest: { value: paths.actions.cursor.grab },
+      dest: { value: rightGripRisingGrab },
       xform: xforms.rising,
       root: rightGripRising,
       priority: 200
     },
     {
       src: { value: rightButton("trigger").pressed },
-      dest: { value: paths.actions.cursor.grab },
+      dest: { value: rightTriggerRisingGrab },
       xform: xforms.rising,
       root: rightTriggerRising,
       priority: 200
+    },
+    {
+      src: [rightGripRisingGrab, rightTriggerRisingGrab],
+      dest: { value: paths.actions.cursor.grab },
+      xform: xforms.any
     }
   ],
 
@@ -443,17 +449,22 @@ export const oculusTouchUserBindings = {
   [sets.rightHandHoveringOnInteractable]: [
     {
       src: { value: rightButton("grip").pressed },
-      dest: { value: paths.actions.rightHand.grab },
+      dest: { value: rightGripRisingGrab },
       xform: xforms.rising,
       root: rightGripRising,
       priority: 200
     },
     {
       src: { value: rightButton("trigger").pressed },
-      dest: { value: paths.actions.rightHand.grab },
+      dest: { value: rightTriggerRisingGrab },
       xform: xforms.rising,
       root: rightTriggerRising,
       priority: 200
+    },
+    {
+      src: [rightGripRisingGrab, rightTriggerRisingGrab],
+      dest: { value: paths.actions.cursor.grab },
+      xform: xforms.any
     }
   ],
 
@@ -480,15 +491,7 @@ export const oculusTouchUserBindings = {
       xform: xforms.any
     }
   ],
-  [sets.rightHandHoveringOnPen]: [
-    {
-      src: { value: rightButton("trigger").pressed },
-      dest: { value: paths.actions.rightHand.grab },
-      xform: xforms.rising,
-      root: rightTriggerRising,
-      priority: 300
-    }
-  ],
+  [sets.rightHandHoveringOnPen]: [],
   [sets.rightHandHoldingPen]: [
     {
       src: { value: rightButton("trigger").pressed },
@@ -549,33 +552,9 @@ export const oculusTouchUserBindings = {
     }
   ],
 
-  [sets.cursorHoveringOnCamera]: [
-    {
-      src: { value: rightButton("trigger").pressed },
-      dest: { value: paths.actions.cursor.grab },
-      xform: xforms.rising,
-      root: rightTriggerRising,
-      priority: 300
-    }
-  ],
-  [sets.rightHandHoveringOnCamera]: [
-    {
-      src: { value: rightButton("trigger").pressed },
-      dest: { value: paths.actions.rightHand.grab },
-      xform: xforms.rising,
-      root: rightTriggerRising,
-      priority: 300
-    }
-  ],
-  [sets.leftHandHoveringOnCamera]: [
-    {
-      src: { value: leftButton("trigger").pressed },
-      dest: { value: paths.actions.leftHand.grab },
-      xform: xforms.rising,
-      root: leftTriggerRising,
-      priority: 300
-    }
-  ],
+  [sets.cursorHoveringOnCamera]: [],
+  [sets.rightHandHoveringOnCamera]: [],
+  [sets.leftHandHoveringOnCamera]: [],
 
   [sets.rightHandHoldingCamera]: [
     {
diff --git a/src/systems/userinput/bindings/vive-user.js b/src/systems/userinput/bindings/vive-user.js
index 3400cb48bd5f9fa059ceb44da8dfd454b9ed74ae..1a94b245f848417411b5ad9cd4d9cc81086afe7f 100644
--- a/src/systems/userinput/bindings/vive-user.js
+++ b/src/systems/userinput/bindings/vive-user.js
@@ -2,30 +2,116 @@ import { paths } from "../paths";
 import { sets } from "../sets";
 import { xforms } from "./xforms";
 
-const rButton = paths.device.vive.left.button;
-const rAxis = paths.device.vive.left.axis;
-
 const v = name => {
   return `/vive-user/vive-var/${name}`;
 };
+
+const lButton = paths.device.vive.left.button;
+const lAxis = paths.device.vive.left.axis;
+const lPose = paths.device.vive.left.pose;
+const lJoy = v("left/joy");
+const lJoyScaled = v("left/joy/scaled");
+const lJoyY = v("left/joyY");
+const lJoyYCursorMod = v("left/joyY/cursorMod");
+const lJoyXScaled = v("left/joyX/scaled");
+const lJoyYScaled = v("left/joyY/scaled");
+const lDpadNorth = v("left/dpad/north");
+const lDpadSouth = v("left/dpad/south");
+const lDpadEast = v("left/dpad/east");
+const lDpadWest = v("left/dpad/west");
+const lDpadCenter = v("left/dpad/center");
+const lTriggerFalling = v("left/trigger/falling");
+const lTriggerRising = v("left/trigger/rising");
+const lTriggerRisingGrab = v("right/trigger/rising/grab");
+const lGripRisingGrab = v("right/grab/rising/grab");
+const lTouchpadRising = v("left/touchpad/rising");
+const lCharacterAcceleration = v("left/characterAcceleration");
+const lGripFalling = v("left/grip/falling");
+const lGripRising = v("left/grip/rising");
+const lGripPressed = v("left/grip/pressed");
+const leftBoost = v("left/boost");
+const lTriggerStartTeleport = v("left/trigger/startTeleport");
+const lDpadCenterStartTeleport = v("left/dpadCenter/startTeleport");
+const lTriggerStopTeleport = v("left/trigger/stopTeleport");
+const lTouchpadStopTeleport = v("left/touchpad/stopTeleport");
+
+const rButton = paths.device.vive.right.button;
+const rAxis = paths.device.vive.right.axis;
+const rPose = paths.device.vive.right.pose;
 const rJoy = v("right/joy");
-const rDpadNorth = v("/right/dpad/north");
-const rDpadSouth = v("/right/dpad/south");
-const rDpadEast = v("/right/dpad/east");
-const rDpadWest = v("/right/dpad/west");
-const rDpadCenter = v("/right/dpad/center");
+const rJoyY = v("right/joyY");
+const rJoyYCursorMod = v("right/joyY/cursorMod");
+const rJoyXScaled = v("right/joyX/scaled");
+const rJoyYScaled = v("right/joyY/scaled");
+const rDpadNorth = v("right/dpad/north");
+const rDpadSouth = v("right/dpad/south");
+const rDpadEast = v("right/dpad/east");
+const rDpadWest = v("right/dpad/west");
+const rDpadCenter = v("right/dpad/center");
+const rTriggerFalling = v("right/trigger/falling");
+const rTriggerRising = v("right/trigger/rising");
+const rTouchpadFalling = v("right/touchpad/falling");
+const rTouchpadRising = v("right/touchpad/rising");
+const rightBoost = v("right/boost");
+const rGripRising = v("right/grip/rising");
+const rTriggerRisingGrab = v("right/trigger/rising/grab");
+const rGripRisingGrab = v("right/grab/rising/grab");
+const rGripFalling = v("right/grip/rising");
+const rGripPressed = v("right/grip/pressed");
+const cursorDrop1 = v("right/cursorDrop1");
+const cursorDrop2 = v("right/cursorDrop2");
+const rHandDrop1 = v("right/drop1");
+const rHandDrop2 = v("right/drop2");
+const rTriggerStartTeleport = v("right/trigger/startTeleport");
+const rDpadCenterStartTeleport = v("right/dpadCenter/startTeleport");
+const rTriggerStopTeleport = v("right/trigger/stopTeleport");
+const rTouchpadStopTeleport = v("right/touchpad/stopTeleport");
+
 const rSnapRight = v("right/snap-right");
-const rSnapLeft = v("left/snap-left");
-const rTouchpadRising = v("right/touchpad-rising");
+const rSnapLeft = v("right/snap-left");
 
 const k = name => {
   return `/vive-user/keyboard-var/${name}`;
 };
 const keyboardSnapRight = k("snap-right");
 const keyboardSnapLeft = k("snap-left");
+const keyboardCharacterAcceleration = k("characterAcceleration");
+const keyboardBoost = k("boost");
 
 export const viveUserBindings = {
   [sets.global]: [
+    {
+      src: {
+        value: paths.device.keyboard.key("b")
+      },
+      dest: {
+        value: paths.actions.toggleScreenShare
+      },
+      xform: xforms.rising
+    },
+    {
+      src: {
+        x: lAxis("joyX"),
+        y: lAxis("joyY")
+      },
+      dest: {
+        value: lJoy
+      },
+      xform: xforms.compose_vec2
+    },
+    {
+      src: {
+        value: lJoy
+      },
+      dest: {
+        north: lDpadNorth,
+        south: lDpadSouth,
+        east: lDpadEast,
+        west: lDpadWest,
+        center: lDpadCenter
+      },
+      xform: xforms.vec2dpad(0.2, false, true)
+    },
     {
       src: {
         x: rAxis("joyX"),
@@ -54,7 +140,7 @@ export const viveUserBindings = {
         value: rButton("touchpad").pressed
       },
       dest: {
-        rTouchpadRising
+        value: rTouchpadRising
       },
       xform: xforms.rising
     },
@@ -80,36 +166,533 @@ export const viveUserBindings = {
       dest: { value: paths.actions.snapRotateRight },
       xform: xforms.any
     },
+    {
+      src: {
+        bool: rTouchpadRising,
+        value: rDpadWest
+      },
+      dest: {
+        value: rSnapLeft
+      },
+      xform: xforms.copyIfTrue,
+      root: rDpadWest,
+      priority: 100
+    },
+    {
+      src: { value: paths.device.keyboard.key("q") },
+      dest: { value: keyboardSnapLeft },
+      xform: xforms.rising
+    },
+    {
+      src: [rSnapLeft, keyboardSnapLeft],
+      dest: { value: paths.actions.snapRotateLeft },
+      xform: xforms.any
+    },
+
+    {
+      src: {
+        value: lAxis("joyX")
+      },
+      dest: {
+        value: lJoyXScaled
+      },
+      xform: xforms.scale(1.5) // horizontal character speed modifier
+    },
+    {
+      src: {
+        value: lAxis("joyY")
+      },
+      dest: { value: lJoyYScaled },
+      xform: xforms.scale(1.5) // vertical character speed modifier
+    },
+    {
+      src: {
+        x: lJoyXScaled,
+        y: lJoyYScaled
+      },
+      dest: { value: lJoyScaled },
+      xform: xforms.compose_vec2
+    },
+    {
+      src: {
+        bool: lButton("touchpad").pressed,
+        value: lJoyScaled
+      },
+      dest: { value: lCharacterAcceleration },
+      xform: xforms.copyIfTrue
+    },
+    {
+      src: {
+        w: paths.device.keyboard.key("w"),
+        a: paths.device.keyboard.key("a"),
+        s: paths.device.keyboard.key("s"),
+        d: paths.device.keyboard.key("d")
+      },
+      dest: { vec2: keyboardCharacterAcceleration },
+      xform: xforms.wasd_to_vec2
+    },
+    {
+      src: {
+        first: lCharacterAcceleration,
+        second: keyboardCharacterAcceleration
+      },
+      dest: {
+        value: paths.actions.characterAcceleration
+      },
+      xform: xforms.add_vec2
+    },
+    {
+      src: { value: paths.device.keyboard.key("shift") },
+      dest: { value: keyboardBoost },
+      xform: xforms.copy
+    },
+    {
+      src: {
+        value: lButton("top").pressed
+      },
+      dest: {
+        value: leftBoost
+      },
+      xform: xforms.copy
+    },
+    {
+      src: {
+        value: rButton("top").pressed
+      },
+      dest: {
+        value: rightBoost
+      },
+      xform: xforms.copy
+    },
+    {
+      src: [keyboardBoost, leftBoost, rightBoost],
+      dest: { value: paths.actions.boost },
+      xform: xforms.any
+    },
+    {
+      src: { value: rPose },
+      dest: { value: paths.actions.cursor.pose },
+      xform: xforms.copy
+    },
+    {
+      src: { value: rPose },
+      dest: { value: paths.actions.rightHand.pose },
+      xform: xforms.copy
+    },
+    {
+      src: { value: lPose },
+      dest: { value: paths.actions.leftHand.pose },
+      xform: xforms.copy
+    },
+    {
+      src: { value: lButton("trigger").pressed },
+      dest: { value: paths.actions.leftHand.stopTeleport },
+      xform: xforms.falling,
+      root: lTriggerFalling,
+      priority: 100
+    }
+  ],
+  [sets.rightHandTeleporting]: [
+    {
+      src: { value: rButton("trigger").pressed },
+      dest: { value: rTriggerStopTeleport },
+      xform: xforms.falling,
+      root: rTriggerFalling,
+      priority: 100
+    },
+    {
+      src: { value: rButton("touchpad").pressed },
+      dest: { value: rTouchpadStopTeleport },
+      xform: xforms.falling
+    },
+    {
+      src: [rTriggerStopTeleport, rTouchpadStopTeleport],
+      dest: { value: paths.actions.rightHand.stopTeleport },
+      xform: xforms.any
+    }
+  ],
+  [sets.leftHandHoveringOnNothing]: [
+    {
+      src: { value: lButton("trigger").pressed },
+      dest: { value: lTriggerStartTeleport },
+      xform: xforms.rising,
+      root: lTriggerRising,
+      priority: 100
+    },
+    {
+      src: {
+        bool: lTouchpadRising,
+        value: lDpadCenter
+      },
+      dest: { value: lDpadCenterStartTeleport },
+      xform: xforms.copyIfTrue
+    },
+    {
+      src: [lTriggerStartTeleport, lDpadCenterStartTeleport],
+      dest: { value: paths.actions.leftHand.startTeleport },
+      xform: xforms.any
+    }
+  ],
+  [sets.leftHandTeleporting]: [
+    {
+      src: { value: lButton("trigger").pressed },
+      dest: { value: lTriggerStopTeleport },
+      xform: xforms.falling,
+      root: lTriggerFalling,
+      priority: 100
+    },
+    {
+      src: { value: lButton("touchpad").pressed },
+      dest: { value: lTouchpadStopTeleport },
+      xform: xforms.falling
+    },
+    {
+      src: [lTriggerStopTeleport, lTouchpadStopTeleport],
+      dest: { value: paths.actions.leftHand.stopTeleport },
+      xform: xforms.any
+    }
+  ],
+
+  [sets.rightHandHoveringOnNothing]: [
+    {
+      src: { value: rButton("trigger").pressed },
+      dest: { value: rTriggerStartTeleport },
+      xform: xforms.rising,
+      root: rTriggerRising,
+      priority: 100
+    },
+    {
+      src: {
+        bool: rTouchpadRising,
+        value: rDpadCenter
+      },
+      dest: { value: rDpadCenterStartTeleport },
+      xform: xforms.copyIfTrue
+    },
+    {
+      src: [rTriggerStartTeleport, rDpadCenterStartTeleport],
+      dest: { value: paths.actions.rightHand.startTeleport },
+      xform: xforms.any
+    }
+  ],
+
+  [sets.cursorHoveringOnNothing]: [],
+
+  [sets.cursorHoveringOnUI]: [
+    {
+      src: { value: rButton("trigger").pressed },
+      dest: { value: paths.actions.cursor.grab },
+      xform: xforms.rising,
+      root: rTriggerRising,
+      priority: 100
+    }
+  ],
+
+  [sets.leftHandHoveringOnInteractable]: [
+    {
+      src: { value: lButton("grip").pressed },
+      dest: { value: lGripRisingGrab },
+      xform: xforms.rising,
+      root: lGripRising,
+      priority: 200
+    },
+    {
+      src: { value: lButton("trigger").pressed },
+      dest: { value: lTriggerRisingGrab },
+      xform: xforms.rising,
+      root: lTriggerRising,
+      priority: 200
+    },
+    {
+      src: [lGripRisingGrab, lTriggerRisingGrab],
+      dest: { value: paths.actions.leftHand.grab },
+      xform: xforms.any
+    }
+  ],
+
+  [sets.leftHandHoldingInteractable]: [
+    {
+      src: { value: lButton("grip").pressed },
+      dest: { value: paths.actions.leftHand.drop },
+      xform: xforms.falling,
+      root: lGripFalling,
+      priority: 200
+    }
+  ],
+
+  [sets.leftHandHoveringOnPen]: [],
+  [sets.leftHandHoldingPen]: [
+    {
+      src: { value: lButton("trigger").pressed },
+      dest: { value: paths.actions.leftHand.startDrawing },
+      xform: xforms.rising
+    },
+    {
+      src: { value: lButton("trigger").pressed },
+      dest: { value: paths.actions.leftHand.stopDrawing },
+      xform: xforms.falling
+    },
+    {
+      src: {
+        value: lButton("touchpad").pressed
+      },
+      dest: {
+        value: lTouchpadRising
+      },
+      xform: xforms.rising
+    },
+    {
+      src: {
+        bool: lTouchpadRising,
+        value: lDpadEast
+      },
+      dest: {
+        value: paths.actions.leftHand.penNextColor
+      },
+      xform: xforms.copyIfTrue,
+      root: lDpadEast,
+      priority: 200
+    },
+    {
+      src: {
+        bool: lTouchpadRising,
+        value: lDpadWest
+      },
+      dest: {
+        value: paths.actions.leftHand.penPrevColor
+      },
+      xform: xforms.copyIfTrue,
+      root: lDpadWest,
+      priority: 200
+    },
+    {
+      src: {
+        value: lAxis("joyX"),
+        touching: lButton("touchpad").touched
+      },
+      dest: { value: paths.actions.leftHand.scalePenTip },
+      xform: xforms.touch_axis_scroll(0.1)
+    }
+  ],
+
+  [sets.cursorHoveringOnInteractable]: [
+    {
+      src: { value: rButton("grip").pressed },
+      dest: { value: rGripRisingGrab },
+      xform: xforms.rising,
+      root: rGripRising,
+      priority: 200
+    },
+    {
+      src: { value: rButton("trigger").pressed },
+      dest: { value: rTriggerRisingGrab },
+      xform: xforms.rising,
+      root: rTriggerRising,
+      priority: 200
+    },
+    {
+      src: [rGripRisingGrab, rTriggerRisingGrab],
+      dest: { value: paths.actions.cursor.grab },
+      xform: xforms.any
+    }
+  ],
+
+  [sets.cursorHoldingInteractable]: [
+    {
+      src: {
+        value: rAxis("joyY"),
+        touching: rButton("touchpad").touched
+      },
+      dest: { value: paths.actions.cursor.modDelta },
+      xform: xforms.touch_axis_scroll(-1)
+    },
+    {
+      src: { value: rButton("grip").pressed },
+      dest: { value: cursorDrop1 },
+      xform: xforms.falling,
+      root: rGripFalling,
+      priority: 200
+    },
+    {
+      src: { value: rButton("trigger").pressed },
+      dest: {
+        value: cursorDrop2
+      },
+      xform: xforms.falling,
+      root: rTriggerFalling,
+      priority: 200
+    },
+    {
+      src: [cursorDrop1, cursorDrop2],
+      dest: { value: paths.actions.cursor.drop },
+      xform: xforms.any
+    }
+  ],
+
+  [sets.cursorHoveringOnPen]: [],
+
+  [sets.cursorHoldingPen]: [
+    {
+      src: { value: rButton("trigger").pressed },
+      dest: { value: paths.actions.cursor.startDrawing },
+      xform: xforms.rising
+    },
+    {
+      src: { value: rButton("trigger").pressed },
+      dest: { value: paths.actions.cursor.stopDrawing },
+      xform: xforms.falling,
+      root: rTriggerFalling,
+      priority: 300
+    },
+    {
+      src: {
+        value: rAxis("joyX"),
+        touching: rButton("touchpad").touched
+      },
+      dest: { value: paths.actions.cursor.scalePenTip },
+      xform: xforms.touch_axis_scroll(-0.1)
+    }
+  ],
+
+  [sets.rightHandHoveringOnInteractable]: [
+    {
+      src: { value: rButton("grip").pressed },
+      dest: { value: rGripRisingGrab },
+      xform: xforms.rising,
+      root: rGripRising,
+      priority: 200
+    },
+    {
+      src: { value: rButton("trigger").pressed },
+      dest: { value: rTriggerRisingGrab },
+      xform: xforms.rising,
+      root: rTriggerRising,
+      priority: 200
+    },
+    {
+      src: [rGripRisingGrab, rTriggerRisingGrab],
+      dest: { value: paths.actions.rightHand.grab },
+      xform: xforms.any
+    }
+  ],
+
+  [sets.rightHandHoldingInteractable]: [
+    {
+      src: { value: rButton("grip").pressed },
+      dest: { value: rHandDrop1 },
+      xform: xforms.falling,
+      root: rGripFalling,
+      priority: 200
+    },
+    {
+      src: { value: rButton("trigger").pressed },
+      dest: {
+        value: rHandDrop2
+      },
+      xform: xforms.falling,
+      root: rTriggerFalling,
+      priority: 200
+    },
+    {
+      src: [rHandDrop1, rHandDrop2],
+      dest: { value: paths.actions.rightHand.drop },
+      xform: xforms.any
+    }
+  ],
+  [sets.rightHandHoveringOnPen]: [],
+  [sets.rightHandHoldingPen]: [
+    {
+      src: { value: rButton("trigger").pressed },
+      dest: { value: paths.actions.rightHand.startDrawing },
+      xform: xforms.rising
+    },
+    {
+      src: { value: rButton("trigger").pressed },
+      dest: { value: paths.actions.rightHand.stopDrawing },
+      xform: xforms.falling,
+      root: rTriggerFalling,
+      priority: 300
+    },
     {
       src: {
         value: rButton("touchpad").pressed
       },
       dest: {
-        rTouchpadRising
+        value: rTouchpadRising
       },
       xform: xforms.rising
     },
+    {
+      src: {
+        bool: rTouchpadRising,
+        value: rDpadEast
+      },
+      dest: {
+        value: paths.actions.rightHand.penNextColor
+      },
+      xform: xforms.copyIfTrue,
+      root: rDpadEast,
+      priority: 200
+    },
     {
       src: {
         bool: rTouchpadRising,
         value: rDpadWest
       },
       dest: {
-        value: rSnapLeft
+        value: paths.actions.rightHand.penPrevColor
       },
       xform: xforms.copyIfTrue,
       root: rDpadWest,
-      priority: 100
+      priority: 200
     },
     {
-      src: { value: paths.device.keyboard.key("q") },
-      dest: { value: keyboardSnapLeft },
+      src: {
+        value: rAxis("joyX"),
+        touching: rButton("touchpad").touched
+      },
+      dest: { value: paths.actions.rightHand.scalePenTip },
+      xform: xforms.touch_axis_scroll(0.1)
+    }
+  ],
+
+  [sets.cursorHoveringOnCamera]: [],
+  [sets.rightHandHoveringOnCamera]: [],
+  [sets.leftHandHoveringOnCamera]: [],
+
+  [sets.rightHandHoldingCamera]: [
+    {
+      src: { value: rButton("trigger").pressed },
+      dest: { value: paths.actions.rightHand.takeSnapshot },
       xform: xforms.rising
     },
     {
-      src: [rSnapLeft, keyboardSnapLeft],
-      dest: { value: paths.actions.snapRotateLeft },
-      xform: xforms.any
+      src: { value: rButton("trigger").pressed },
+      dest: { value: paths.noop },
+      xform: xforms.falling,
+      root: rTriggerFalling,
+      priority: 400
+    }
+  ],
+  [sets.leftHandHoldingCamera]: [
+    {
+      src: { value: lButton("trigger").pressed },
+      dest: { value: paths.actions.leftHand.takeSnapshot },
+      xform: xforms.rising
+    }
+  ],
+  [sets.cursorHoldingCamera]: [
+    {
+      src: { value: rButton("trigger").pressed },
+      dest: { value: paths.actions.cursor.takeSnapshot },
+      xform: xforms.rising
+    },
+    {
+      src: { value: rButton("trigger").pressed },
+      dest: { value: paths.noop },
+      xform: xforms.falling,
+      root: rTriggerFalling,
+      priority: 400
     }
   ]
 };
diff --git a/src/systems/userinput/bindings/xforms.js b/src/systems/userinput/bindings/xforms.js
index 33f9c8b752478fae0c8606f989874dd05bc56809..8fe0d49d22ed58513bd50cf65fb8e7a104905b41 100644
--- a/src/systems/userinput/bindings/xforms.js
+++ b/src/systems/userinput/bindings/xforms.js
@@ -41,7 +41,6 @@ export const xforms = {
     frame[dest.value] = true;
   },
   rising: function rising(frame, src, dest, prevState) {
-    if (frame[dest.value]) return true;
     frame[dest.value] = frame[src.value] && prevState === false;
     return !!frame[src.value];
   },
diff --git a/src/systems/userinput/devices/vive-controller.js b/src/systems/userinput/devices/vive-controller.js
index e493b87ba4bf5b8a9621d7fd9e88deb7e32bcfa4..9f97c5b48ae671e449d7a99350d2abb0ebbfcc54 100644
--- a/src/systems/userinput/devices/vive-controller.js
+++ b/src/systems/userinput/devices/vive-controller.js
@@ -19,8 +19,12 @@ export class ViveControllerDevice {
     this.gamepad = gamepad;
     this.pose = new Pose();
     this.axisMap = [{ name: "joyX", axisId: 0 }, { name: "joyY", axisId: 1 }];
-    this.path = paths.device.vive[gamepad.hand];
-    this.selector = `[super-hands]#player-${gamepad.hand}-controller`;
+    this.path = paths.device.vive[gamepad.hand || "right"];
+    if (!gamepad.hand) {
+      console.warn("gamepad detected without hand specified");
+    } else {
+      this.selector = `[super-hands]#player-${gamepad.hand}-controller`;
+    }
   }
   write(frame) {
     if (!this.gamepad.connected) return;
@@ -45,7 +49,23 @@ export class ViveControllerDevice {
       frame[this.path.axis(axis.name)] = frame[paths.device.gamepad(this.gamepad.index).axis(axis.axisId)];
     });
 
-    const rayObject = document.querySelector(this.selector).object3D;
+    if (!this.selector) {
+      if (this.gamepad.hand) {
+        this.path = paths.device.vive[this.gamepad.hand];
+        this.selector = `[super-hands]#player-${this.gamepad.hand}-controller`;
+        console.warn("gamepad hand eventually specified");
+      } else {
+        return;
+      }
+    }
+    const el = document.querySelector(this.selector);
+    if (el === "null") {
+    }
+    if (el.components["tracked-controls"].controller !== this.gamepad) {
+      el.components["tracked-controls"].controller = this.gamepad;
+      el.setAttribute("tracked-controls", "controller", this.gamepad.index);
+    }
+    const rayObject = el.object3D;
     rayObject.updateMatrixWorld();
     this.rayObjectRotation.setFromRotationMatrix(rayObject.matrixWorld);
     this.pose.position.setFromMatrixPosition(rayObject.matrixWorld);
diff --git a/src/systems/userinput/paths.js b/src/systems/userinput/paths.js
index dd1289351570c92cefc20451a3d93da9de389c82..28a8fa9111c436eaeaeff1408e236143dfffc134 100644
--- a/src/systems/userinput/paths.js
+++ b/src/systems/userinput/paths.js
@@ -1,13 +1,16 @@
 export const paths = {};
 paths.noop = "/noop";
 paths.actions = {};
+paths.actions.log = "/actions/log";
 paths.actions.toggleScreenShare = "/actions/toggleScreenShare";
+paths.actions.snapRotateLeft = "/actions/snapRotateLeft";
+paths.actions.snapRotateRight = "/actions/snapRotateRight";
 paths.actions.logDebugFrame = "/actions/logDebugFrame";
 paths.actions.cameraDelta = "/actions/cameraDelta";
 paths.actions.characterAcceleration = "/actions/characterAcceleration";
-paths.actions.snapRotateLeft = "/actions/snapRotateLeft";
-paths.actions.snapRotateRight = "/actions/snapRotateRight";
 paths.actions.boost = "/actions/boost";
+paths.actions.startGazeTeleport = "/actions/startTeleport";
+paths.actions.stopGazeTeleport = "/actions/stopTeleport";
 paths.actions.translate = {};
 paths.actions.translate.forward = "/actions/translate/forward";
 paths.actions.translate.backward = "/actions/translate/backward";
@@ -26,8 +29,6 @@ paths.actions.cursor.penPrevColor = "/actions/cursorPenPrevColor";
 paths.actions.cursor.scalePenTip = "/actions/cursorScalePenTip";
 paths.actions.cursor.scaleGrabbedGrabbable = "/actions/cursorScaleGrabbedGrabbable";
 paths.actions.cursor.takeSnapshot = "/actions/cursorTakeSnapshot";
-paths.actions.startTeleport = "/actions/startTeleport";
-paths.actions.stopTeleport = "/actions/stopTeleport";
 paths.actions.rightHand = {};
 paths.actions.rightHand.pose = "/actions/rightHandPose";
 paths.actions.rightHand.grab = "/actions/rightHandGrab";
diff --git a/src/systems/userinput/userinput.js b/src/systems/userinput/userinput.js
index 1780aceca31c8104e5def0488a2d86a3b0d9629a..27b9e35664fb83ac1fa816ff187ea5eaa89e0268 100644
--- a/src/systems/userinput/userinput.js
+++ b/src/systems/userinput/userinput.js
@@ -127,7 +127,14 @@ AFRAME.registerSystem("userinput", {
         console.log(e.gamepad);
         let gamepadDevice;
         if (e.gamepad.id === "OpenVR Gamepad") {
-          gamepadDevice = new ViveControllerDevice(e.gamepad);
+          for (var i = 0; i < this.activeDevices.length; i++) {
+            const activeDevice = this.activeDevices[i];
+            if (activeDevice.gamepad && activeDevice.gamepad === e.gamepad) {
+              console.warn("ignoring gamepad");
+              return; // multiple connect events without a disconnect event
+            }
+          }
+          if (this.activeDevices) gamepadDevice = new ViveControllerDevice(e.gamepad);
           this.registeredMappings.add(viveUserBindings);
         } else if (e.gamepad.id.startsWith("Oculus Touch")) {
           gamepadDevice = new OculusTouchControllerDevice(e.gamepad);
@@ -193,7 +200,7 @@ AFRAME.registerSystem("userinput", {
 
     this.frame = frame;
     this.activeBindings = activeBindings;
-    if (frame[paths.actions.logDebugFrame]) {
+    if (frame[paths.actions.logDebugFrame] || frame[paths.actions.log]) {
       console.log("frame", this.frame);
       console.log("sets", this.activeSets);
       console.log("bindings", this.activeBindings);