diff --git a/src/components/pin-networked-object-button.js b/src/components/pin-networked-object-button.js
index 3b7ae88cc77d697499087033721eee24676fc8e0..c6e0fb1299b50f8ee38624a92b08c3b4afb3ca3a 100644
--- a/src/components/pin-networked-object-button.js
+++ b/src/components/pin-networked-object-button.js
@@ -1,8 +1,5 @@
 AFRAME.registerComponent("pin-networked-object-button", {
   schema: {
-    // Selector for root of all UI that needs to be clickable when pinned
-    uiSelector: { type: "string" },
-
     // Selector for label to change when pinned/unpinned, must be sibling of this components element
     labelSelector: { type: "string" },
 
diff --git a/src/components/pitch-yaw-rotator.js b/src/components/pitch-yaw-rotator.js
index a2c2ebdead1d580c4480e2578e59c918289941f0..d3a400905a6869f71539083325e00e663f2cecc0 100644
--- a/src/components/pitch-yaw-rotator.js
+++ b/src/components/pitch-yaw-rotator.js
@@ -5,8 +5,8 @@ const radToDeg = THREE.Math.radToDeg;
 
 AFRAME.registerComponent("pitch-yaw-rotator", {
   schema: {
-    minPitch: { default: -50 },
-    maxPitch: { default: 50 }
+    minPitch: { default: -65 },
+    maxPitch: { default: 65 }
   },
 
   init() {
diff --git a/src/components/stop-event-propagation.js b/src/components/stop-event-propagation.js
index b12be3fa99da119e4e548ae77e9b5f991f6825ac..a4ed7a311b239ee750fd1ae8b70771922e75b983 100644
--- a/src/components/stop-event-propagation.js
+++ b/src/components/stop-event-propagation.js
@@ -1,6 +1,5 @@
 const handler = e => {
   e.stopPropagation();
-  e.preventDefault();
 };
 
 AFRAME.registerComponent("stop-event-propagation", {
diff --git a/src/hub.html b/src/hub.html
index 6b469e1351330ac006b602b9f8481ecb58c402e4..cbf570006c377befe03545ef45ee2bc720b89000 100644
--- a/src/hub.html
+++ b/src/hub.html
@@ -189,9 +189,9 @@
                     emit-state-change__ungrabbed="state: grabbed; transform: falling; event: ungrabbed;"
                     emit-state-change__pinned="state: pinned; transform: rising; event: pinned;"
                 >
-                    <a-entity class="interactable-ui" stop-event-propagation__grab-start="event: grab-start" stop-event-propagation__grab-end="event: grab-end">
+                    <a-entity class="ui interactable-ui" stop-event-propagation__grab-start="event: grab-start" stop-event-propagation__grab-end="event: grab-end" stop-event-propagation__stretch-start="event: stretch-start" stop-event-propagation__stretch-end="event: stretch-end">
                         <a-entity class="freeze-menu" visible-while-frozen="withinDistance: 10;">
-                            <a-entity mixin="rounded-text-action-button" pin-networked-object-button="labelSelector:.pin-button-label; hideWhenPinnedSelector:.hide-when-pinned; uiSelector:.interactable-ui" position="0 0.125 0.01"> </a-entity>
+                            <a-entity mixin="rounded-text-action-button" pin-networked-object-button="labelSelector:.pin-button-label; hideWhenPinnedSelector:.hide-when-pinned;" position="0 0.125 0.01"> </a-entity>
                             <a-entity class="pin-button-label" text=" value:pin; width:1.75; align:center;" text-raycast-hack position="0 0.125 0.02"></a-entity>
                             <a-entity mixin="rounded-text-button" class="hide-when-pinned" remove-networked-object-button position="0 -0.125 0.01"> </a-entity>
                             <a-entity text=" value:remove; width:1.75; align:center;" class="hide-when-pinned" text-raycast-hack position="0 -0.125 0.02"></a-entity>
@@ -230,7 +230,7 @@
                         segments-width="16"
                         segments-height="12"
                     ></a-sphere>
-                    <a-entity class="delete-button" visible-while-frozen="withinDistance: 10;">
+                    <a-entity class="ui delete-button" visible-while-frozen="withinDistance: 10;" stop-event-propagation__grab-start="event: grab-start" stop-event-propagation__grab-end="event: grab-end" stop-event-propagation__stretch-start="event: stretch-start" stop-event-propagation__stretch-end="event: stretch-end">
                         <a-entity mixin="rounded-text-button" remove-networked-object-button position="0 0 0"> </a-entity>
                         <a-entity text=" value:remove; width:2.5; align:center;" text-raycast-hack position="0 0 0.01"></a-entity>
                     </a-entity>
@@ -254,7 +254,7 @@
                     set-yxz-order
                     auto-scale-cannon-physics-body
                 >
-                    <a-entity class="delete-button" visible-while-frozen="withinDistance: 10;">
+                    <a-entity class="ui delete-button" visible-while-frozen="withinDistance: 10;" stop-event-propagation__grab-start="event: grab-start" stop-event-propagation__grab-end="event: grab-end" stop-event-propagation__stretch-start="event: stretch-start" stop-event-propagation__stretch-end="event: stretch-end">
                         <a-entity mixin="rounded-text-button" remove-networked-object-button position="0 0 0"> </a-entity>
                         <a-entity text=" value:remove; width:2.5; align:center;" text-raycast-hack position="0 0 0.01"></a-entity>
                     </a-entity>
@@ -268,7 +268,7 @@
             </template>
 
             <template id="paging-toolbar">
-                <a-entity class="paging-toolbar" visible-to-owner>
+                <a-entity class="ui paging-toolbar" visible-to-owner>
                     <a-entity class="prev-button" position="-0.3 0 0">
                         <a-entity mixin="rounded-text-button" slice9="width: 0.2"> </a-entity>
                         <a-entity text=" value:<; width:2; align:center;" text-raycast-hack position="0 0 0.01"></a-entity>
diff --git a/src/hub.js b/src/hub.js
index a9ac40c324785aab8218a2d871b54e401365c0e5..9bb097fdd44c7585f2b9a05add813dd068fa487e 100644
--- a/src/hub.js
+++ b/src/hub.js
@@ -86,6 +86,7 @@ import "./systems/personal-space-bubble";
 import "./systems/app-mode";
 import "./systems/exit-on-blur";
 import "./systems/userinput/userinput";
+import "./systems/userinput/userinput-debug";
 
 import "./gltf-component-mappings";
 
diff --git a/src/systems/userinput/bindings/daydream-user.js b/src/systems/userinput/bindings/daydream-user.js
index e2cb60984febd107b84de14d50bfe13147af2094..86581e1041c07920d98175e50901f856150427ae 100644
--- a/src/systems/userinput/bindings/daydream-user.js
+++ b/src/systems/userinput/bindings/daydream-user.js
@@ -1,50 +1,45 @@
 import { paths } from "../paths";
 import { sets } from "../sets";
 import { xforms } from "./xforms";
+import { addSetsToBindings } from "./utils";
 
 // vars
 const v = s => `/vars/daydream/${s}`;
-const touchpad = v("touchpad");
-const touchpadPressed = v("touchpadPressed");
-const touchpadReleased = v("touchpadReleased");
+const touchpad = v("touchpad/axis");
+const touchpadRising = v("touchpad/rising");
+const touchpadFalling = v("touchpad/falling");
+const touchpadPressed = v("touchpad/pressed");
 const dpadNorth = v("dpad/north");
 const dpadSouth = v("dpad/south");
 const dpadEast = v("dpad/east");
 const dpadWest = v("dpad/west");
 const dpadCenter = v("dpad/center");
-const vec2zero = "/vars/vec2zero";
 const brushSizeDelta = v("brushSizeDelta");
 const cursorModDelta = v("cursorModDelta");
-const dpadSouthDrop = v("dpad/southDrop");
-const dpadCenterDrop = v("dpad/centerDrop");
-
-// roots
-const dpadEastRoot = "daydreamDpadEast";
-const dpadWestRoot = "daydreamDpadWest";
-const dpadCenterRoot = "daydreamDpadCenter";
-const touchpadFallingRoot = "daydreamTouchpadFalling";
-const cursorModDeltaRoot = "daydreamCursorModDeltaRoot";
+const dpadSouthDrop = v("dropSouth");
+const dpadCenterDrop = v("dropCenter");
 
 const grabBinding = [
   {
-    src: { value: dpadCenter, bool: touchpadPressed },
+    src: { value: dpadCenter, bool: touchpadRising },
     dest: { value: paths.actions.cursor.grab },
     xform: xforms.copyIfTrue,
-    root: dpadCenterRoot,
-    priority: 200
+    priority: 100
   }
 ];
 
 const dropOnCenterOrSouth = [
   {
-    src: { value: dpadCenter, bool: touchpadPressed },
+    src: { value: dpadCenter, bool: touchpadRising },
     dest: { value: dpadCenterDrop },
-    xform: xforms.copyIfTrue
+    xform: xforms.copyIfTrue,
+    priority: 100
   },
   {
-    src: { value: dpadSouth, bool: touchpadPressed },
+    src: { value: dpadSouth, bool: touchpadRising },
     dest: { value: dpadSouthDrop },
-    xform: xforms.copyIfTrue
+    xform: xforms.copyIfTrue,
+    priority: 100
   },
   {
     src: [dpadCenterDrop, dpadSouthDrop],
@@ -53,7 +48,7 @@ const dropOnCenterOrSouth = [
   }
 ];
 
-export const daydreamUserBindings = {
+export const daydreamUserBindings = addSetsToBindings({
   [sets.global]: [
     {
       src: {
@@ -67,16 +62,23 @@ export const daydreamUserBindings = {
       src: {
         value: paths.device.daydream.button("touchpad").pressed
       },
-      dest: { value: touchpadPressed },
+      dest: { value: touchpadRising },
       xform: xforms.rising
     },
     {
       src: {
         value: paths.device.daydream.button("touchpad").pressed
       },
-      dest: { value: touchpadReleased },
+      dest: { value: touchpadFalling },
       xform: xforms.falling
     },
+    {
+      src: {
+        value: paths.device.daydream.button("touchpad").pressed
+      },
+      dest: { value: touchpadPressed },
+      xform: xforms.copy
+    },
     {
       src: {
         value: touchpad
@@ -93,40 +95,30 @@ export const daydreamUserBindings = {
     {
       src: {
         value: dpadEast,
-        bool: touchpadPressed
+        bool: touchpadRising
       },
       dest: {
         value: paths.actions.snapRotateRight
       },
-      xform: xforms.copyIfTrue,
-      root: dpadEastRoot,
-      priority: 100
+      xform: xforms.copyIfTrue
     },
     {
       src: {
         value: dpadWest,
-        bool: touchpadPressed
+        bool: touchpadRising
       },
       dest: {
         value: paths.actions.snapRotateLeft
       },
-      xform: xforms.copyIfTrue,
-      root: dpadWestRoot,
-      priority: 100
+      xform: xforms.copyIfTrue
     },
     {
       src: {
         value: dpadCenter,
-        bool: touchpadPressed
+        bool: touchpadRising
       },
       dest: { value: paths.actions.rightHand.startTeleport },
-      xform: xforms.copyIfTrue,
-      root: dpadCenterRoot,
-      priority: 100
-    },
-    {
-      dest: { value: vec2zero },
-      xform: xforms.always([0, -0.2])
+      xform: xforms.copyIfTrue
     },
     {
       src: { value: paths.device.daydream.pose },
@@ -145,11 +137,9 @@ export const daydreamUserBindings = {
 
   [sets.cursorHoldingInteractable]: [
     {
-      src: { value: paths.device.daydream.button("touchpad").pressed },
+      src: { value: touchpadFalling },
       dest: { value: paths.actions.cursor.drop },
-      xform: xforms.falling,
-      root: touchpadFallingRoot,
-      priority: 100
+      xform: xforms.copy
     },
     {
       src: {
@@ -162,36 +152,30 @@ export const daydreamUserBindings = {
     {
       src: { value: cursorModDelta },
       dest: { value: paths.actions.cursor.modDelta },
-      xform: xforms.copy,
-      root: cursorModDeltaRoot,
-      priority: 100
+      xform: xforms.copy
     }
   ],
 
   [sets.rightHandTeleporting]: [
     {
-      src: { value: paths.device.daydream.button("touchpad").pressed },
+      src: { value: touchpadFalling },
       dest: { value: paths.actions.rightHand.stopTeleport },
-      xform: xforms.falling,
-      root: touchpadFallingRoot,
-      priority: 100
+      xform: xforms.copy
     }
   ],
 
   [sets.cursorHoldingPen]: [
     {
-      src: { value: dpadNorth, bool: touchpadPressed },
+      src: { value: dpadNorth, bool: touchpadRising },
       dest: { value: paths.actions.cursor.startDrawing },
       xform: xforms.copyIfTrue,
-      root: dpadCenterRoot,
-      priority: 300
+      priority: 100
     },
     {
-      src: { value: touchpadReleased },
+      src: { value: touchpadFalling },
       dest: { value: paths.actions.cursor.stopDrawing },
       xform: xforms.copy,
-      root: touchpadFallingRoot,
-      priority: 300
+      priority: 100
     },
     {
       src: {
@@ -204,38 +188,36 @@ export const daydreamUserBindings = {
     {
       src: {
         value: brushSizeDelta,
-        bool: paths.device.daydream.button("touchpad").pressed
+        bool: touchpadPressed
       },
       dest: { value: paths.actions.cursor.scalePenTip },
-      xform: xforms.copyIfFalse
+      xform: xforms.copyIfFalse,
+      priority: 100
     },
     {
-      src: { value: dpadEast, bool: touchpadPressed },
+      src: { value: dpadEast, bool: touchpadRising },
       dest: {
         value: paths.actions.cursor.penPrevColor
       },
       xform: xforms.copyIfTrue,
-      root: dpadEastRoot,
-      priority: 200
+      priority: 100
     },
     {
-      src: { value: dpadWest, bool: touchpadPressed },
+      src: { value: dpadWest, bool: touchpadRising },
       dest: {
         value: paths.actions.cursor.penNextColor
       },
       xform: xforms.copyIfTrue,
-      root: dpadWestRoot,
-      priority: 200
+      priority: 100
     },
     {
       src: {
         value: cursorModDelta,
-        bool: paths.device.daydream.button("touchpad").pressed
+        bool: touchpadPressed
       },
       dest: { value: paths.actions.cursor.modDelta },
       xform: xforms.copyIfFalse,
-      root: cursorModDeltaRoot,
-      priority: 200
+      priority: 100
     },
     ...dropOnCenterOrSouth
   ],
@@ -244,22 +226,20 @@ export const daydreamUserBindings = {
     // Don't drop on touchpad release
     {
       src: {
-        value: paths.device.daydream.button("touchpad").pressed
+        value: touchpadFalling
       },
       xform: xforms.noop,
-      root: touchpadFallingRoot,
-      priority: 300
+      priority: 100
     },
     {
       src: {
         value: dpadNorth,
-        bool: touchpadPressed
+        bool: touchpadRising
       },
       dest: { value: paths.actions.cursor.takeSnapshot },
       xform: xforms.copyIfTrue,
-      root: dpadCenterRoot,
-      priority: 300
+      priority: 100
     },
     ...dropOnCenterOrSouth
   ]
-};
+});
diff --git a/src/systems/userinput/bindings/keyboard-debugging.js b/src/systems/userinput/bindings/keyboard-debugging.js
index d1142ee335e348c2f8ba2db71e763cd616528b35..0abcefb21dff477d4f82b59b15a5c57434e54d09 100644
--- a/src/systems/userinput/bindings/keyboard-debugging.js
+++ b/src/systems/userinput/bindings/keyboard-debugging.js
@@ -1,8 +1,9 @@
 import { paths } from "../paths";
 import { sets } from "../sets";
 import { xforms } from "./xforms";
+import { addSetsToBindings } from "./utils";
 
-export const keyboardDebuggingBindings = {
+export const keyboardDebuggingBindings = addSetsToBindings({
   [sets.global]: [
     {
       src: {
@@ -14,4 +15,4 @@ export const keyboardDebuggingBindings = {
       xform: xforms.rising
     }
   ]
-};
+});
diff --git a/src/systems/userinput/bindings/keyboard-mouse-user.js b/src/systems/userinput/bindings/keyboard-mouse-user.js
index 46e5edaf7a22f543e3fbcab3b52ffb8b69312f80..722847b95c40abc78587f4f6ce9bcaff27ea6d6b 100644
--- a/src/systems/userinput/bindings/keyboard-mouse-user.js
+++ b/src/systems/userinput/bindings/keyboard-mouse-user.js
@@ -1,6 +1,7 @@
 import { paths } from "../paths";
 import { sets } from "../sets";
 import { xforms } from "./xforms";
+import { addSetsToBindings } from "./utils";
 
 const wasd_vec2 = "/var/mouse-and-keyboard/wasd_vec2";
 const keyboardCharacterAcceleration = "/var/mouse-and-keyboard/keyboardCharacterAcceleration";
@@ -8,27 +9,11 @@ const arrows_vec2 = "/var/mouse-and-keyboard/arrows_vec2";
 const dropWithRMB = "/vars/mouse-and-keyboard/drop_with_RMB";
 const dropWithEsc = "/vars/mouse-and-keyboard/drop_with_esc";
 
-const dropWithRMBorEscBindings = [
-  {
-    src: { value: paths.device.mouse.buttonRight },
-    dest: { value: dropWithRMB },
-    xform: xforms.falling,
-    root: "rmb",
-    priority: 200
-  },
-  {
-    src: { value: paths.device.keyboard.key("Escape") },
-    dest: { value: dropWithEsc },
-    xform: xforms.falling
-  },
-  {
-    src: [dropWithRMB, dropWithEsc],
-    dest: { value: paths.actions.cursor.drop },
-    xform: xforms.any
-  }
-];
+const k = name => {
+  return `/keyboard-mouse-user/keyboard-var/${name}`;
+};
 
-export const keyboardMouseUserBindings = {
+export const keyboardMouseUserBindings = addSetsToBindings({
   [sets.global]: [
     {
       src: {
@@ -80,16 +65,12 @@ export const keyboardMouseUserBindings = {
     {
       src: { value: paths.device.keyboard.key("q") },
       dest: { value: paths.actions.snapRotateLeft },
-      xform: xforms.rising,
-      root: "q",
-      priority: 100
+      xform: xforms.rising
     },
     {
       src: { value: paths.device.keyboard.key("e") },
       dest: { value: paths.actions.snapRotateRight },
-      xform: xforms.rising,
-      root: "e",
-      priority: 100
+      xform: xforms.rising
     },
     {
       src: { value: paths.device.keyboard.key(" ") },
@@ -166,7 +147,6 @@ export const keyboardMouseUserBindings = {
         value: paths.actions.startGazeTeleport
       },
       xform: xforms.rising,
-      root: "rmb",
       priority: 100
     },
     {
@@ -176,7 +156,8 @@ export const keyboardMouseUserBindings = {
       dest: {
         value: paths.actions.stopGazeTeleport
       },
-      xform: xforms.falling
+      xform: xforms.falling,
+      priority: 100
     }
   ],
 
@@ -219,7 +200,6 @@ export const keyboardMouseUserBindings = {
       src: { value: "/var/notshift+q" },
       dest: { value: paths.actions.snapRotateLeft },
       xform: xforms.rising,
-      root: "q",
       priority: 200
     },
     {
@@ -234,99 +214,141 @@ export const keyboardMouseUserBindings = {
       src: { value: "/var/notshift+e" },
       dest: { value: paths.actions.snapRotateRight },
       xform: xforms.rising,
-      root: "e",
       priority: 200
     },
     {
       src: { value: paths.device.mouse.buttonLeft },
       dest: { value: paths.actions.cursor.startDrawing },
       xform: xforms.rising,
-      priority: 200
+      priority: 3
     },
     {
       src: { value: paths.device.mouse.buttonLeft },
       dest: { value: paths.actions.cursor.stopDrawing },
       xform: xforms.falling,
-      priority: 200,
-      root: "lmb"
+      priority: 3
     },
     {
       src: {
-        bool: paths.device.keyboard.key("shift"),
-        value: paths.device.mouse.wheel
+        value: k("wheelWithShift")
       },
       dest: { value: "/var/cursorScalePenTipWheel" },
-      xform: xforms.copyIfTrue,
-      priority: 200,
-      root: "wheel"
+      xform: xforms.copy,
+      priority: 200
     },
     {
       src: { value: "/var/cursorScalePenTipWheel" },
       dest: { value: paths.actions.cursor.scalePenTip },
-      xform: xforms.scale(0.12)
+      xform: xforms.scale(0.03)
     },
-    ...dropWithRMBorEscBindings
+    {
+      src: { value: paths.device.mouse.buttonRight },
+      dest: { value: dropWithRMB },
+      xform: xforms.falling,
+      priority: 200
+    },
+    {
+      src: { value: paths.device.keyboard.key("Escape") },
+      dest: { value: dropWithEsc },
+      xform: xforms.falling
+    },
+    {
+      src: [dropWithRMB, dropWithEsc],
+      dest: { value: paths.actions.cursor.drop },
+      xform: xforms.any
+    }
   ],
 
   [sets.cursorHoldingCamera]: [
     {
       src: { value: paths.device.mouse.buttonLeft },
       dest: { value: paths.actions.cursor.takeSnapshot },
-      xform: xforms.rising
+      xform: xforms.rising,
+      priority: 3
     },
     {
-      src: { value: paths.device.mouse.buttonLeft },
-      xform: xforms.noop,
-      priority: 200,
-      root: "lmb"
+      src: { value: paths.device.mouse.buttonRight },
+      dest: { value: dropWithRMB },
+      xform: xforms.falling,
+      priority: 200
     },
-    ...dropWithRMBorEscBindings
+    {
+      src: { value: paths.device.keyboard.key("Escape") },
+      dest: { value: dropWithEsc },
+      xform: xforms.falling
+    },
+    {
+      src: [dropWithRMB, dropWithEsc],
+      dest: { value: paths.actions.cursor.drop },
+      xform: xforms.any
+    }
   ],
 
   [sets.cursorHoldingInteractable]: [
     {
       src: {
+        bool: paths.device.keyboard.key("shift"),
         value: paths.device.mouse.wheel
       },
       dest: {
-        value: paths.actions.cursor.modDelta
+        value: k("wheelWithShift")
       },
-      xform: xforms.copy,
-      root: "wheel",
-      priority: 100
+      xform: xforms.copyIfTrue
     },
     {
       src: {
         bool: paths.device.keyboard.key("shift"),
         value: paths.device.mouse.wheel
       },
-      dest: { value: paths.actions.cursor.modDelta },
+      dest: {
+        value: k("wheelWithoutShift")
+      },
       xform: xforms.copyIfFalse
     },
     {
       src: {
-        bool: paths.device.keyboard.key("shift"),
-        value: paths.device.mouse.wheel
+        value: k("wheelWithoutShift")
+      },
+      dest: { value: paths.actions.cursor.modDelta },
+      xform: xforms.copy
+    },
+    {
+      src: {
+        value: k("wheelWithShift")
       },
       dest: { value: paths.actions.cursor.scaleGrabbedGrabbable },
-      xform: xforms.copyIfTrue,
-      priority: 150,
-      root: "wheel"
+      xform: xforms.copy
     },
     {
       src: { value: paths.device.mouse.buttonLeft },
       dest: { value: paths.actions.cursor.drop },
       xform: xforms.falling,
-      priority: 100,
-      root: "lmb"
+      priority: 2
     }
   ],
 
   [sets.cursorHoveringOnInteractable]: [
+    {
+      src: { value: paths.device.mouse.buttonLeft },
+      dest: { value: paths.actions.cursor.grab },
+      xform: xforms.rising,
+      priority: 1
+    }
+  ],
+  [sets.inputFocused]: [
+    {
+      src: { value: "/device/keyboard" },
+      dest: { value: paths.noop },
+      xform: xforms.noop,
+      priority: 1000
+    }
+  ],
+
+  [sets.cursorHoveringOnUI]: [
     {
       src: { value: paths.device.mouse.buttonLeft },
       dest: { value: paths.actions.cursor.grab },
       xform: xforms.rising
     }
   ]
-};
+});
diff --git a/src/systems/userinput/bindings/oculus-go-user.js b/src/systems/userinput/bindings/oculus-go-user.js
index 30cf8301c5ca1b44323cbf95a70f7c94f7063660..4bdeba3eccb83d43e64bd74682a69042c1726f7c 100644
--- a/src/systems/userinput/bindings/oculus-go-user.js
+++ b/src/systems/userinput/bindings/oculus-go-user.js
@@ -1,10 +1,13 @@
 import { paths } from "../paths";
 import { sets } from "../sets";
 import { xforms } from "./xforms";
+import { addSetsToBindings } from "./utils";
 
 const touchpad = "/vars/oculusgo/touchpad";
-const touchpadPressed = "/vars/oculusgo/touchpadPressed";
-const touchpadReleased = "/vars/oculusgo/touchpadReleased";
+const touchpadRising = "/vars/oculusgo/touchpad/rising";
+const touchpadFalling = "/vars/oculusgo/touchpad/falling";
+const triggerRising = "/vars/oculusgo/trigger/rising";
+const triggerFalling = "/vars/oculusgo/trigger/falling";
 const dpadNorth = "/vars/oculusgo/dpad/north";
 const dpadSouth = "/vars/oculusgo/dpad/south";
 const dpadEast = "/vars/oculusgo/dpad/east";
@@ -12,45 +15,54 @@ const dpadWest = "/vars/oculusgo/dpad/west";
 const dpadCenter = "/vars/oculusgo/dpad/center";
 const dpadCenterStrip = "/vars/oculusgo/dpad/centerStrip";
 
-const triggerRisingRoot = "oculusGoTriggerRising";
-const triggerFallingRoot = "oculusGoTriggerFalling";
-const dpadEastRoot = "oculusGoDpadEast";
-const dpadWestRoot = "oculusGoDpadWest";
-const rootForFrozenOverrideWhenHolding = "rootForFrozenOverrideWhenHolding";
-
 const grabBinding = {
   src: {
-    value: paths.device.oculusgo.button("trigger").pressed
+    value: triggerRising
   },
   dest: { value: paths.actions.cursor.grab },
-  xform: xforms.rising,
-  root: triggerRisingRoot,
+  xform: xforms.copy,
   priority: 200
 };
 
-export const oculusGoUserBindings = {
+export const oculusGoUserBindings = addSetsToBindings({
   [sets.global]: [
     {
       src: {
-        x: paths.device.oculusgo.axis("touchpadX"),
-        y: paths.device.oculusgo.axis("touchpadY")
+        value: paths.device.oculusgo.button("trigger").pressed
       },
-      dest: { value: touchpad },
-      xform: xforms.compose_vec2
+      dest: { value: triggerRising },
+      xform: xforms.rising
+    },
+    {
+      src: {
+        value: paths.device.oculusgo.button("trigger").pressed
+      },
+      dest: { value: triggerFalling },
+      xform: xforms.falling
     },
     {
       src: {
         value: paths.device.oculusgo.button("touchpad").pressed
       },
-      dest: { value: touchpadPressed },
-      xform: xforms.rising
+      dest: { value: touchpadRising },
+      xform: xforms.rising,
+      priority: 100
     },
     {
       src: {
         value: paths.device.oculusgo.button("touchpad").pressed
       },
-      dest: { value: touchpadReleased },
-      xform: xforms.falling
+      dest: { value: touchpadFalling },
+      xform: xforms.falling,
+      priority: 100
+    },
+    {
+      src: {
+        x: paths.device.oculusgo.axis("touchpadX"),
+        y: paths.device.oculusgo.axis("touchpadY")
+      },
+      dest: { value: touchpad },
+      xform: xforms.compose_vec2
     },
     {
       src: {
@@ -78,12 +90,11 @@ export const oculusGoUserBindings = {
       dest: {
         value: paths.actions.ensureFrozen
       },
-      root: rootForFrozenOverrideWhenHolding,
       priority: 100,
       xform: xforms.copyIfTrue
     },
     {
-      src: { value: touchpadReleased },
+      src: { value: touchpadFalling },
       dest: {
         value: paths.actions.thaw
       },
@@ -92,35 +103,32 @@ export const oculusGoUserBindings = {
     {
       src: {
         value: dpadEast,
-        bool: touchpadPressed
+        bool: touchpadRising
       },
       dest: {
         value: paths.actions.snapRotateRight
       },
       xform: xforms.copyIfTrue,
-      root: dpadEastRoot,
       priority: 100
     },
     {
       src: {
         value: dpadWest,
-        bool: touchpadPressed
+        bool: touchpadRising
       },
       dest: {
         value: paths.actions.snapRotateLeft
       },
       xform: xforms.copyIfTrue,
-      root: dpadWestRoot,
       priority: 100
     },
 
     {
       src: {
-        value: paths.device.oculusgo.button("trigger").pressed
+        value: triggerRising
       },
       dest: { value: paths.actions.rightHand.startTeleport },
-      xform: xforms.rising,
-      root: triggerRisingRoot,
+      xform: xforms.copy,
       priority: 100
     },
 
@@ -158,11 +166,10 @@ export const oculusGoUserBindings = {
   [sets.cursorHoldingInteractable]: [
     {
       src: {
-        value: paths.device.oculusgo.button("trigger").pressed
+        value: triggerFalling
       },
       dest: { value: paths.actions.cursor.drop },
-      xform: xforms.falling,
-      root: triggerFallingRoot,
+      xform: xforms.copy,
       priority: 200
     },
     {
@@ -174,22 +181,19 @@ export const oculusGoUserBindings = {
       xform: xforms.touch_axis_scroll()
     },
     {
-      src: null,
-      dest: { value: paths.actions.ensureFrozen },
-      root: rootForFrozenOverrideWhenHolding,
+      src: { value: dpadCenterStrip },
       priority: 200,
-      xform: xforms.always(false)
+      xform: xforms.noop
     }
   ],
 
   [sets.rightHandTeleporting]: [
     {
       src: {
-        value: paths.device.oculusgo.button("trigger").pressed
+        value: triggerFalling
       },
       dest: { value: paths.actions.rightHand.stopTeleport },
-      xform: xforms.falling,
-      root: triggerFallingRoot,
+      xform: xforms.copy,
       priority: 100
     }
   ],
@@ -197,20 +201,18 @@ export const oculusGoUserBindings = {
   [sets.cursorHoldingPen]: [
     {
       src: {
-        value: paths.device.oculusgo.button("trigger").pressed
+        value: triggerRising
       },
       dest: { value: paths.actions.cursor.startDrawing },
-      xform: xforms.rising,
-      root: triggerRisingRoot,
+      xform: xforms.copy,
       priority: 300
     },
     {
       src: {
-        value: paths.device.oculusgo.button("trigger").pressed
+        value: triggerFalling
       },
       dest: { value: paths.actions.cursor.stopDrawing },
-      xform: xforms.falling,
-      root: triggerFallingRoot,
+      xform: xforms.copy,
       priority: 300
     },
     {
@@ -224,33 +226,32 @@ export const oculusGoUserBindings = {
     {
       src: {
         value: dpadCenterStrip,
-        bool: touchpadReleased
+        bool: touchpadFalling
       },
       dest: { value: paths.actions.cursor.drop },
-      xform: xforms.copyIfTrue
+      xform: xforms.copyIfTrue,
+      priority: 300
     },
     {
       src: {
         value: dpadEast,
-        bool: touchpadPressed
+        bool: touchpadRising
       },
       dest: {
         value: paths.actions.cursor.penPrevColor
       },
       xform: xforms.copyIfTrue,
-      root: dpadEastRoot,
       priority: 200
     },
     {
       src: {
         value: dpadWest,
-        bool: touchpadPressed
+        bool: touchpadRising
       },
       dest: {
         value: paths.actions.cursor.penNextColor
       },
       xform: xforms.copyIfTrue,
-      root: dpadWestRoot,
       priority: 200
     }
   ],
@@ -258,28 +259,27 @@ export const oculusGoUserBindings = {
   [sets.cursorHoldingCamera]: [
     {
       src: {
-        value: paths.device.oculusgo.button("trigger").pressed
+        value: triggerRising
       },
       dest: { value: paths.actions.cursor.takeSnapshot },
-      xform: xforms.rising,
-      root: triggerRisingRoot,
+      xform: xforms.copy,
       priority: 300
     },
     {
       src: {
-        value: paths.device.oculusgo.button("trigger").pressed
+        value: triggerFalling
       },
       xform: xforms.noop,
-      root: triggerFallingRoot,
       priority: 300
     },
     {
       src: {
         value: dpadCenterStrip,
-        bool: touchpadReleased
+        bool: touchpadFalling
       },
       dest: { value: paths.actions.cursor.drop },
-      xform: xforms.copyIfTrue
+      xform: xforms.copyIfTrue,
+      priority: 300
     }
   ]
-};
+});
diff --git a/src/systems/userinput/bindings/oculus-touch-user.js b/src/systems/userinput/bindings/oculus-touch-user.js
index ecdff220224641915959c90cc69e604daa972b7e..9fb929fbcd524aa086371d57d91792d93ff94066 100644
--- a/src/systems/userinput/bindings/oculus-touch-user.js
+++ b/src/systems/userinput/bindings/oculus-touch-user.js
@@ -1,6 +1,7 @@
 import { paths } from "../paths";
 import { sets } from "../sets";
 import { xforms } from "./xforms";
+import { addSetsToBindings } from "./utils";
 
 const name = "/touch/var/";
 
@@ -13,30 +14,21 @@ const rightPose = paths.device.rightOculusTouch.pose;
 
 const scaledLeftJoyX = `${name}left/scaledJoyX`;
 const scaledLeftJoyY = `${name}left/scaledJoyY`;
-const rightGripFalling = "${name}right/GripFalling";
-const rightTriggerFalling = `${name}right/TriggerFalling`;
 const cursorDrop2 = `${name}right/cursorDrop2`;
 const cursorDrop1 = `${name}right/cursorDrop1`;
 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`;
-const leftTriggerFalling = `${name}left/TriggerFalling`;
 const rightDpadNorth = `${name}rightDpad/north`;
 const rightDpadSouth = `${name}rightDpad/south`;
 const rightDpadEast = `${name}rightDpad/east`;
 const rightDpadWest = `${name}rightDpad/west`;
 const rightDpadCenter = `${name}rightDpad/center`;
 const rightJoy = `${name}right/joy`;
-const rightJoyY = `${name}right/joyY`;
-const rightJoyYCursorMod = `${name}right/joyYCursorMod`;
+const rightJoyY1 = `${name}right/joyY1`;
+const rightJoyY2 = `${name}right/joyY2`;
 const leftDpadNorth = `${name}leftDpad/north`;
 const leftDpadSouth = `${name}leftDpad/south`;
 const leftDpadEast = `${name}leftDpad/east`;
@@ -44,7 +36,6 @@ const leftDpadWest = `${name}leftDpad/west`;
 const leftDpadCenter = `${name}leftDpad/center`;
 const leftJoy = `${name}left/joy`;
 const leftJoyY = `${name}left/joyY`;
-const leftJoyYCursorMod = `${name}left/joyYCursorMod`;
 const oculusTouchCharacterAcceleration = `${name}characterAcceleration`;
 const keyboardCharacterAcceleration = "/var/keyboard/characterAcceleration";
 const characterAcceleration = "/var/oculus-touch/nonNormalizedCharacterAcceleration";
@@ -58,8 +49,6 @@ const rightTouchSnapLeft = `${name}/right/snap-left`;
 const keyboardSnapRight = `${name}/keyboard/snap-right`;
 const keyboardSnapLeft = `${name}/keyboard/snap-left`;
 
-const rootForFrozenOverrideWhenHolding = "rootForFrozenOverrideWhenHolding";
-
 const lowerButtons = `${name}buttons/lower`;
 
 const ensureFrozenViaButtons = `${name}buttons/ensureFrozen`;
@@ -68,12 +57,52 @@ const ensureFrozenViaKeyboard = `${name}keyboard/ensureFrozen`;
 const thawViaButtons = `${name}buttons/thaw`;
 const thawViaKeyboard = `${name}keyboard/thaw`;
 
-export const oculusTouchUserBindings = {
+const v = s => {
+  return "/oculus-touch-user-vars/" + s;
+};
+const leftGripPressed1 = v("leftGripPressed1");
+const leftGripPressed2 = v("leftGripPressed2");
+const rightGripPressed1 = v("rightGripPressed1");
+const rightGripPressed2 = v("rightGripPressed2");
+const leftTriggerPressed1 = v("leftTriggerPressed1");
+const leftTriggerPressed2 = v("leftTriggerPressed2");
+const rightTriggerPressed1 = v("rightTriggerPressed1");
+const rightTriggerPressed2 = v("rightTriggerPressed2");
+
+export const oculusTouchUserBindings = addSetsToBindings({
   [sets.global]: [
+    {
+      src: [ensureFrozenViaButtons, ensureFrozenViaKeyboard],
+      dest: { value: paths.actions.ensureFrozen },
+      xform: xforms.any
+    },
+    {
+      src: [thawViaButtons, thawViaKeyboard],
+      dest: { value: paths.actions.thaw },
+      xform: xforms.any
+    },
     {
       src: {
         value: leftButton("grip").pressed
       },
+      dest: {
+        value: leftGripPressed1
+      },
+      xform: xforms.copy
+    },
+    {
+      src: {
+        value: leftButton("grip").pressed
+      },
+      dest: {
+        value: leftGripPressed2
+      },
+      xform: xforms.copy
+    },
+    {
+      src: {
+        value: leftGripPressed1
+      },
       dest: {
         value: paths.actions.leftHand.middleRingPinky
       },
@@ -88,6 +117,20 @@ export const oculusTouchUserBindings = {
     },
     {
       src: { value: leftButton("trigger").pressed },
+      dest: {
+        value: leftTriggerPressed1
+      },
+      xform: xforms.copy
+    },
+    {
+      src: { value: leftButton("trigger").pressed },
+      dest: {
+        value: leftTriggerPressed2
+      },
+      xform: xforms.copy
+    },
+    {
+      src: { value: leftTriggerPressed1 },
       dest: {
         value: paths.actions.leftHand.index
       },
@@ -97,6 +140,24 @@ export const oculusTouchUserBindings = {
       src: {
         value: rightButton("grip").pressed
       },
+      dest: {
+        value: rightGripPressed1
+      },
+      xform: xforms.copy
+    },
+    {
+      src: {
+        value: rightButton("grip").pressed
+      },
+      dest: {
+        value: rightGripPressed2
+      },
+      xform: xforms.copy
+    },
+    {
+      src: {
+        value: rightGripPressed1
+      },
       dest: {
         value: paths.actions.rightHand.middleRingPinky
       },
@@ -111,6 +172,20 @@ export const oculusTouchUserBindings = {
     },
     {
       src: { value: rightButton("trigger").pressed },
+      dest: {
+        value: rightTriggerPressed1
+      },
+      xform: xforms.copy
+    },
+    {
+      src: { value: rightButton("trigger").pressed },
+      dest: {
+        value: rightTriggerPressed2
+      },
+      xform: xforms.copy
+    },
+    {
+      src: { value: rightTriggerPressed1 },
       dest: {
         value: paths.actions.rightHand.index
       },
@@ -148,10 +223,24 @@ export const oculusTouchUserBindings = {
       },
       xform: xforms.vec2dpad(0.2, false, true)
     },
+    {
+      src: {
+        value: rightAxis("joyY")
+      },
+      dest: { value: rightJoyY1 },
+      xform: xforms.copy
+    },
+    {
+      src: {
+        value: rightAxis("joyY")
+      },
+      dest: { value: rightJoyY2 },
+      xform: xforms.copy
+    },
     {
       src: {
         x: rightAxis("joyX"),
-        y: rightAxis("joyY")
+        y: rightJoyY1
       },
       dest: {
         value: rightJoy
@@ -179,8 +268,7 @@ export const oculusTouchUserBindings = {
         value: rightTouchSnapRight
       },
       xform: xforms.rising,
-      root: rightDpadEast,
-      priority: 100
+      priority: 1
     },
     {
       src: { value: paths.device.keyboard.key("e") },
@@ -205,7 +293,6 @@ export const oculusTouchUserBindings = {
     {
       src: { value: lowerButtons },
       dest: { value: ensureFrozenViaButtons },
-      root: rootForFrozenOverrideWhenHolding,
       xform: xforms.copy
     },
     {
@@ -226,8 +313,7 @@ export const oculusTouchUserBindings = {
         value: rightTouchSnapLeft
       },
       xform: xforms.rising,
-      root: rightDpadWest,
-      priority: 100
+      priority: 1
     },
     {
       src: { value: paths.device.keyboard.key("q") },
@@ -283,33 +369,6 @@ export const oculusTouchUserBindings = {
       dest: { vec2: wasd_vec2 },
       xform: xforms.wasd_to_vec2
     },
-    {
-      src: {
-        value: paths.device.keyboard.key("t")
-      },
-      dest: {
-        value: paths.actions.focusChat
-      },
-      xform: xforms.rising
-    },
-    {
-      src: {
-        value: paths.device.keyboard.key("l")
-      },
-      dest: {
-        value: paths.actions.logDebugFrame
-      },
-      xform: xforms.rising
-    },
-    {
-      src: {
-        value: paths.device.keyboard.key("m")
-      },
-      dest: {
-        value: paths.actions.muteMic
-      },
-      xform: xforms.rising
-    },
     {
       src: {
         first: wasd_vec2,
@@ -329,7 +388,10 @@ export const oculusTouchUserBindings = {
       xform: xforms.max_vec2
     },
     {
-      src: { value: characterAcceleration },
+      src: {
+        value: characterAcceleration,
+        override: "/device/overrides/foo"
+      },
       dest: { value: paths.actions.characterAcceleration },
       xform: xforms.normalize_vec2
     },
@@ -361,6 +423,33 @@ export const oculusTouchUserBindings = {
       dest: { value: paths.actions.boost },
       xform: xforms.any
     },
+    {
+      src: {
+        value: paths.device.keyboard.key("t")
+      },
+      dest: {
+        value: paths.actions.focusChat
+      },
+      xform: xforms.rising
+    },
+    {
+      src: {
+        value: paths.device.keyboard.key("l")
+      },
+      dest: {
+        value: paths.actions.logDebugFrame
+      },
+      xform: xforms.rising
+    },
+    {
+      src: {
+        value: paths.device.keyboard.key("m")
+      },
+      dest: {
+        value: paths.actions.muteMic
+      },
+      xform: xforms.rising
+    },
     {
       src: { value: rightPose },
       dest: { value: paths.actions.cursor.pose },
@@ -377,86 +466,76 @@ export const oculusTouchUserBindings = {
       xform: xforms.copy
     },
     {
-      src: { value: rightButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: { value: paths.actions.rightHand.stopTeleport },
       xform: xforms.falling,
-      root: rightTriggerFalling,
-      priority: 100
+      priority: 1
     },
     {
-      src: { value: leftButton("trigger").pressed },
+      src: { value: leftTriggerPressed2 },
       dest: { value: paths.actions.leftHand.stopTeleport },
       xform: xforms.falling,
-      root: leftTriggerFalling,
-      priority: 100
+      priority: 1
     }
   ],
 
   [sets.leftHandHoveringOnNothing]: [
     {
-      src: { value: leftButton("trigger").pressed },
+      src: { value: leftTriggerPressed2 },
       dest: { value: paths.actions.leftHand.startTeleport },
       xform: xforms.rising,
-      root: leftTriggerRising,
-      priority: 100
+      priority: 1
     }
   ],
 
   [sets.cursorHoveringOnUI]: [
     {
-      src: { value: rightButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: { value: paths.actions.cursor.grab },
       xform: xforms.rising,
-      root: rightTriggerRising,
-      priority: 100
+      priority: 2
     }
   ],
 
   [sets.cursorHoveringOnNothing]: [
     {
-      src: { value: rightButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: { value: paths.actions.rightHand.startTeleport },
       xform: xforms.rising,
-      root: rightTriggerRising,
-      priority: 100
+      priority: 1
     }
   ],
 
   [sets.leftHandHoveringOnInteractable]: [
     {
-      src: { value: leftButton("grip").pressed },
+      src: { value: leftGripPressed2 },
       dest: { value: leftGripRisingGrab },
-      xform: xforms.rising,
-      root: leftGripRising,
-      priority: 200
+      xform: xforms.rising
     },
     {
-      src: { value: leftButton("trigger").pressed },
+      src: { value: leftTriggerPressed2 },
       dest: { value: leftTriggerRisingGrab },
       xform: xforms.rising,
-      root: leftTriggerRising,
-      priority: 200
+      priority: 2
     },
     {
-      src: [leftGripRisingGrab, leftTriggerRisingGrab],
+      src: [leftGripRisingGrab],
       dest: { value: paths.actions.leftHand.grab },
-      xform: xforms.any
+      xform: xforms.any,
+      priority: 2
     }
   ],
 
   [sets.leftHandHoldingInteractable]: [
     {
-      src: { value: leftButton("grip").pressed },
+      src: { value: leftGripPressed2 },
       dest: { value: paths.actions.leftHand.drop },
-      xform: xforms.falling,
-      root: leftGripFalling,
-      priority: 200
+      xform: xforms.falling
     },
     {
-      src: null,
+      src: {},
       dest: { value: ensureFrozenViaButtons },
-      root: rootForFrozenOverrideWhenHolding,
-      priority: 100,
+      priority: 1,
       xform: xforms.always(false)
     }
   ],
@@ -464,81 +543,72 @@ export const oculusTouchUserBindings = {
   [sets.leftHandHoveringOnPen]: [],
   [sets.leftHandHoldingPen]: [
     {
-      src: { value: leftButton("trigger").pressed },
+      src: { value: leftTriggerPressed2 },
       dest: { value: paths.actions.leftHand.startDrawing },
-      xform: xforms.rising
+      xform: xforms.rising,
+      priority: 3
     },
     {
-      src: { value: leftButton("trigger").pressed },
+      src: { value: leftTriggerPressed2 },
       dest: { value: paths.actions.leftHand.stopDrawing },
-      xform: xforms.falling
+      xform: xforms.falling,
+      priority: 3
     },
     {
       src: {
-        value: leftDpadEast
+        value: leftDpadEast,
+        override: "/device/overrides/foo"
       },
       dest: {
         value: paths.actions.leftHand.penNextColor
       },
       xform: xforms.rising,
-      root: leftDpadEast,
-      priority: 200
+      priority: 2
     },
     {
       src: {
-        value: leftDpadWest
+        value: leftDpadWest,
+        override: "/device/overrides/foo"
       },
       dest: {
         value: paths.actions.leftHand.penPrevColor
       },
       xform: xforms.rising,
-      root: leftDpadWest,
-      priority: 200
+      priority: 2
     },
     {
       src: {
-        bool: leftButton("grip").pressed,
         value: leftAxis("joyY")
       },
       dest: { value: leftJoyY },
-      xform: xforms.copyIfTrue
+      xform: xforms.copy
     },
     {
       src: { value: leftJoyY },
       dest: { value: paths.actions.leftHand.scalePenTip },
-      xform: xforms.scale(-0.01)
-    },
-    {
-      src: {
-        boo: leftButton("grip").pressed,
-        value: leftAxis("joyY")
-      },
-      dest: { value: leftJoyYCursorMod },
-      xform: xforms.copyIfFalse,
-      root: leftJoyY,
-      priority: 100
+      xform: xforms.scale(-0.005),
+      priority: 1
     }
   ],
 
   [sets.cursorHoveringOnInteractable]: [
     {
-      src: { value: rightButton("grip").pressed },
+      src: { value: rightGripPressed2 },
       dest: { value: rightGripRisingGrab },
       xform: xforms.rising,
-      root: rightGripRising,
-      priority: 200
+      priority: 2
     },
     {
-      src: { value: rightButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: { value: rightTriggerRisingGrab },
       xform: xforms.rising,
-      root: rightTriggerRising,
-      priority: 200
+      priority: 2
     },
     {
-      src: [rightGripRisingGrab, rightTriggerRisingGrab],
+      src: [rightGripRisingGrab],
       dest: { value: paths.actions.cursor.grab },
-      xform: xforms.any
+      xform: xforms.any,
+      priority: 2
     }
   ],
 
@@ -549,31 +619,29 @@ export const oculusTouchUserBindings = {
       xform: xforms.scale(0.1)
     },
     {
-      src: { value: rightButton("grip").pressed },
+      src: { value: rightGripPressed2 },
       dest: { value: cursorDrop1 },
       xform: xforms.falling,
-      root: rightGripFalling,
-      priority: 200
+      priority: 3
     },
     {
-      src: { value: rightButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: {
         value: cursorDrop2
       },
       xform: xforms.falling,
-      root: rightTriggerFalling,
-      priority: 200
+      priority: 2
     },
     {
-      src: [cursorDrop1, cursorDrop2],
+      src: [cursorDrop1],
       dest: { value: paths.actions.cursor.drop },
-      xform: xforms.any
+      xform: xforms.any,
+      priority: 2
     },
     {
-      src: null,
+      src: {},
       dest: { value: ensureFrozenViaButtons },
-      root: rootForFrozenOverrideWhenHolding,
-      priority: 100,
+      priority: 1,
       xform: xforms.always(false)
     }
   ],
@@ -582,84 +650,75 @@ export const oculusTouchUserBindings = {
 
   [sets.cursorHoldingPen]: [
     {
-      src: { value: rightButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: { value: paths.actions.cursor.startDrawing },
-      xform: xforms.rising
+      xform: xforms.rising,
+      priority: 3
     },
     {
-      src: { value: rightButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: { value: paths.actions.cursor.stopDrawing },
       xform: xforms.falling,
-      root: rightTriggerFalling,
-      priority: 300
+      priority: 3
     }
   ],
 
   [sets.rightHandHoveringOnInteractable]: [
     {
-      src: { value: rightButton("grip").pressed },
+      src: { value: rightGripPressed2 },
       dest: { value: rightGripRisingGrab },
       xform: xforms.rising,
-      root: rightGripRising,
-      priority: 200
+      priority: 2
     },
     {
-      src: { value: rightButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: { value: rightTriggerRisingGrab },
       xform: xforms.rising,
-      root: rightTriggerRising,
-      priority: 200
+      priority: 2
     },
     {
-      src: [rightGripRisingGrab, rightTriggerRisingGrab],
+      src: [rightGripRisingGrab],
       dest: { value: paths.actions.rightHand.grab },
-      xform: xforms.any
+      xform: xforms.any,
+      priority: 2
     }
   ],
 
   [sets.rightHandHoldingInteractable]: [
     {
-      src: { value: rightButton("grip").pressed },
-      dest: { value: rightHandDrop1 },
-      xform: xforms.falling,
-      root: rightGripFalling,
-      priority: 200
-    },
-    {
-      src: { value: rightButton("trigger").pressed },
+      src: { value: rightGripPressed2 },
       dest: {
         value: rightHandDrop2
       },
       xform: xforms.falling,
-      root: rightTriggerFalling,
-      priority: 200
+      priority: 3
     },
     {
-      src: [rightHandDrop1, rightHandDrop2],
+      src: [rightHandDrop2],
       dest: { value: paths.actions.rightHand.drop },
-      xform: xforms.any
+      xform: xforms.any,
+      priority: 2
     },
     {
-      src: null,
+      src: {},
       dest: { value: ensureFrozenViaButtons },
-      root: rootForFrozenOverrideWhenHolding,
-      priority: 100,
+      priority: 1,
       xform: xforms.always(false)
     }
   ],
   [sets.rightHandHoveringOnPen]: [],
   [sets.rightHandHoldingPen]: [
     {
-      src: { value: rightButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: { value: paths.actions.rightHand.startDrawing },
-      xform: xforms.rising
+      xform: xforms.rising,
+      priority: 3
     },
     {
-      src: { value: rightButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: { value: paths.actions.rightHand.stopDrawing },
       xform: xforms.falling,
-      root: rightTriggerFalling,
-      priority: 300
+      priority: 3
     },
     {
       src: {
@@ -669,8 +728,7 @@ export const oculusTouchUserBindings = {
         value: paths.actions.rightHand.penNextColor
       },
       xform: xforms.rising,
-      root: rightDpadEast,
-      priority: 200
+      priority: 2
     },
     {
       src: {
@@ -680,31 +738,19 @@ export const oculusTouchUserBindings = {
         value: paths.actions.rightHand.penPrevColor
       },
       xform: xforms.rising,
-      root: rightDpadWest,
-      priority: 200
+      priority: 2
     },
     {
-      src: {
-        bool: rightButton("grip").pressed,
-        value: rightAxis("joyY")
-      },
-      dest: { value: rightJoyY },
-      xform: xforms.copyIfTrue
-    },
-    {
-      src: { value: rightJoyY },
+      src: { value: rightJoyY2 },
       dest: { value: paths.actions.rightHand.scalePenTip },
-      xform: xforms.scale(-0.01)
+      xform: xforms.scale(-0.005),
+      priority: 2
     },
     {
-      src: {
-        boo: rightButton("grip").pressed,
-        value: rightAxis("joyY")
-      },
-      dest: { value: rightJoyYCursorMod },
-      xform: xforms.copyIfFalse,
-      root: rightJoyY,
-      priority: 100
+      src: { value: rightGripPressed2 },
+      dest: { value: paths.actions.rightHand.drop },
+      xform: xforms.falling,
+      priority: 3
     }
   ],
 
@@ -714,51 +760,36 @@ export const oculusTouchUserBindings = {
 
   [sets.rightHandHoldingCamera]: [
     {
-      src: { value: rightButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: { value: paths.actions.rightHand.takeSnapshot },
-      xform: xforms.rising
-    },
-    {
-      src: { value: rightButton("trigger").pressed },
-      dest: { value: paths.noop },
-      xform: xforms.falling,
-      root: rightTriggerFalling,
-      priority: 400
+      xform: xforms.rising,
+      priority: 4
     }
   ],
   [sets.leftHandHoldingCamera]: [
     {
-      src: { value: leftButton("trigger").pressed },
+      src: { value: leftTriggerPressed2 },
       dest: { value: paths.actions.leftHand.takeSnapshot },
-      xform: xforms.rising
+      xform: xforms.rising,
+      priority: 4
     }
   ],
   [sets.cursorHoldingCamera]: [
     {
-      src: { value: rightButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: { value: paths.actions.cursor.takeSnapshot },
-      xform: xforms.rising
-    },
-    {
-      src: { value: rightButton("trigger").pressed },
-      dest: { value: paths.noop },
-      xform: xforms.falling,
-      root: rightTriggerFalling,
-      priority: 400
+      xform: xforms.rising,
+      priority: 4
     }
   ],
 
   [sets.rightHandHoveringOnNothing]: [],
-  [sets.globalPost]: [
+  [sets.inputFocused]: [
     {
-      src: [ensureFrozenViaButtons, ensureFrozenViaKeyboard],
-      dest: { value: paths.actions.ensureFrozen },
-      xform: xforms.any
-    },
-    {
-      src: [thawViaButtons, thawViaKeyboard],
-      dest: { value: paths.actions.thaw },
-      xform: xforms.any
+      src: { value: "/device/keyboard" },
+      dest: { value: paths.noop },
+      xform: xforms.noop,
+      priority: 1000
     }
   ]
-};
+});
diff --git a/src/systems/userinput/bindings/touchscreen-user.js b/src/systems/userinput/bindings/touchscreen-user.js
index 41503259f5452dda91467b00127c7f400b8a77d2..f96a8558a2d5bd410e76cf0219bc64134fb1e932 100644
--- a/src/systems/userinput/bindings/touchscreen-user.js
+++ b/src/systems/userinput/bindings/touchscreen-user.js
@@ -1,6 +1,7 @@
 import { paths } from "../paths";
 import { sets } from "../sets";
 import { xforms } from "./xforms";
+import { addSetsToBindings } from "./utils";
 
 const zero = "/vars/touchscreen/zero";
 const forward = "/vars/touchscreen/pinchDeltaForward";
@@ -13,7 +14,7 @@ const gyroCamDelta = "vars/gyro/gyroCameraDelta";
 const gyroCamDeltaXScaled = "vars/gyro/gyroCameraDelta/x/scaled";
 const gyroCamDeltaYScaled = "vars/gyro/gyroCameraDelta/y/scaled";
 
-export const touchscreenUserBindings = {
+export const touchscreenUserBindings = addSetsToBindings({
   [sets.global]: [
     {
       src: { value: paths.device.touchscreen.pinch.delta },
@@ -21,6 +22,7 @@ export const touchscreenUserBindings = {
       xform: xforms.scale(0.25)
     },
     {
+      src: {},
       dest: { value: zero },
       xform: xforms.always(0)
     },
@@ -80,16 +82,12 @@ export const touchscreenUserBindings = {
     {
       src: { value: paths.device.touchscreen.isTouchingGrabbable },
       dest: { value: paths.actions.cursor.grab },
-      xform: xforms.copy,
-      root: "touchscreen.isTouchingGrabbable",
-      priority: 100
+      xform: xforms.copy
     },
     {
       src: { value: paths.device.hud.penButton },
       dest: { value: paths.actions.spawnPen },
-      xform: xforms.rising,
-      root: "hud.penButton",
-      priority: 100
+      xform: xforms.rising
     }
   ],
   [sets.cursorHoldingInteractable]: [
@@ -97,36 +95,29 @@ export const touchscreenUserBindings = {
       src: { value: paths.device.touchscreen.isTouchingGrabbable },
       dest: { value: paths.actions.cursor.drop },
       xform: xforms.falling,
-      root: "touchscreen.cursorDrop",
-      priority: 100
+      priority: 1
     }
   ],
 
   [sets.cursorHoveringOnPen]: [],
   [sets.cursorHoldingPen]: [
-    {
-      src: { value: paths.device.touchscreen.isTouchingGrabbable },
-      dest: { value: paths.noop },
-      xform: xforms.noop,
-      root: "touchscreen.cursorDrop",
-      priority: 200
-    },
     {
       src: { value: paths.device.touchscreen.isTouchingGrabbable },
       dest: { value: paths.actions.cursor.startDrawing },
-      xform: xforms.risingWithFrameDelay(5)
+      xform: xforms.risingWithFrameDelay(5),
+      priority: 2
     },
     {
       src: { value: paths.device.touchscreen.isTouchingGrabbable },
       dest: { value: paths.actions.cursor.stopDrawing },
-      xform: xforms.falling
+      xform: xforms.falling,
+      priority: 2
     },
     {
       src: { value: paths.device.hud.penButton },
       dest: { value: paths.actions.cursor.drop },
       xform: xforms.rising,
-      root: "hud.penButton",
-      priority: 200
+      priority: 1
     }
   ]
-};
+});
diff --git a/src/systems/userinput/bindings/utils.js b/src/systems/userinput/bindings/utils.js
new file mode 100644
index 0000000000000000000000000000000000000000..324190ec0545fbc02a45fc8c8a56d4585ceeccb7
--- /dev/null
+++ b/src/systems/userinput/bindings/utils.js
@@ -0,0 +1,11 @@
+export const addSetsToBindings = mapping => {
+  for (const setName in mapping) {
+    for (const binding of mapping[setName]) {
+      if (!binding.sets) {
+        binding.sets = [];
+      }
+      binding.sets.push(setName);
+    }
+  }
+  return mapping;
+};
diff --git a/src/systems/userinput/bindings/vive-user.js b/src/systems/userinput/bindings/vive-user.js
index 173de070272ac170101076a86e3a166222c886fe..16772c1bce3b995cae157477393e23eed0320d73 100644
--- a/src/systems/userinput/bindings/vive-user.js
+++ b/src/systems/userinput/bindings/vive-user.js
@@ -1,6 +1,7 @@
 import { paths } from "../paths";
 import { sets } from "../sets";
 import { xforms } from "./xforms";
+import { addSetsToBindings } from "./utils";
 
 const v = name => {
   return `/vive-user/vive-var/${name}`;
@@ -18,17 +19,14 @@ 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 lTriggerFallingStopDrawing = v("left/trigger/falling/stopDrawing");
 const lGripFallingStopDrawing = v("left/grip/falling/stopDrawing");
-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 lTouchpadRising2 = v("left/touchpad/rising2");
+const lTouchpadRising1 = v("left/touchpad/rising1");
 const lCharacterAcceleration = v("left/characterAcceleration");
 const characterAcceleration = v("nonNormalizedCharacterAcceleration");
-const lGripFalling = v("left/grip/falling");
-const lGripRising = v("left/grip/rising");
 const leftBoost = v("left/boost");
 
 const rButton = paths.device.vive.right.button;
@@ -41,15 +39,11 @@ const rDpadEast = v("right/dpad/east");
 const rDpadWest = v("right/dpad/west");
 const rDpadCenter = v("right/dpad/center");
 const rDpadCenterStrip = v("right/dpad/centerStrip");
-const rTriggerFalling = v("right/trigger/falling");
-const rTriggerRising = v("right/trigger/rising");
 const rTouchpadRising = v("right/touchpad/rising");
 const rTouchpadFalling = v("right/touchpad/falling");
 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/falling");
 const cursorDrop1 = v("right/cursorDrop1");
 const cursorDrop2 = v("right/cursorDrop2");
 const rHandDrop1 = v("right/drop1");
@@ -77,36 +71,59 @@ const wasd_vec2 = k("wasd_vec2");
 const arrows_vec2 = k("arrows_vec2");
 const keyboardBoost = k("boost");
 
-const nothingHeldLeft = [
-  {
-    src: { value: lButton("trigger").pressed },
-    dest: { value: paths.actions.leftHand.startTeleport },
-    xform: xforms.rising,
-    root: lTriggerRising,
-    priority: 100
-  }
-];
-const nothingHeldRight = [
-  {
-    src: { value: rButton("trigger").pressed },
-    dest: { value: paths.actions.rightHand.startTeleport },
-    xform: xforms.rising,
-    root: rTriggerRising,
-    priority: 100
-  }
-];
+const leftGripPressed1 = v("leftGripPressed1");
+const leftGripPressed2 = v("leftGripPressed2");
+const rightGripPressed1 = v("rightGripPressed1");
+const rightGripPressed2 = v("rightGripPressed2");
+const leftTriggerPressed1 = v("leftTriggerPressed1");
+const leftTriggerPressed2 = v("leftTriggerPressed2");
+const leftTouchpadPressed1 = v("leftTouchpadPressed1");
+const leftTouchpadPressed2 = v("leftTouchpadPressed2");
+const rightTriggerPressed1 = v("rightTriggerPressed1");
+const rightTriggerPressed2 = v("rightTriggerPressed2");
+const leftTouchpadFallingStopTeleport = v("leftTouchpadFallingStopTeleport");
+const leftTriggerFallingStopTeleport = v("leftTriggerFallingStopTeleport");
+const leftGripFallingWhileHoldingPen = v("leftGripFallingWhileHoldingPen");
 
-export const viveUserBindings = {
+export const viveUserBindings = addSetsToBindings({
   [sets.global]: [
+    {
+      src: [ensureFrozenViaDpad, ensureFrozenViaKeyboard],
+      dest: { value: paths.actions.ensureFrozen },
+      xform: xforms.any
+    },
+    {
+      src: [thawViaDpad, thawViaKeyboard],
+      dest: { value: paths.actions.thaw },
+      xform: xforms.any
+    },
     {
       src: {
-        value: lButton("grip").touched
+        value: lButton("grip").pressed
+      },
+      dest: {
+        value: leftGripPressed1
+      },
+      xform: xforms.copy
+    },
+    {
+      src: {
+        value: leftGripPressed1
       },
       dest: {
         value: paths.actions.leftHand.middleRingPinky
       },
       xform: xforms.copy
     },
+    {
+      src: {
+        value: lButton("grip").pressed
+      },
+      dest: {
+        value: leftGripPressed2
+      },
+      xform: xforms.copy
+    },
     {
       src: [lButton("touchpad").touched, lButton("top").touched],
       dest: {
@@ -115,15 +132,47 @@ export const viveUserBindings = {
       xform: xforms.any
     },
     {
-      src: { value: lButton("trigger").pressed },
+      src: { value: leftTriggerPressed1 },
       dest: {
         value: paths.actions.leftHand.index
       },
       xform: xforms.copy
     },
+    {
+      src: { value: lButton("trigger").pressed },
+      dest: {
+        value: leftTriggerPressed1
+      },
+      xform: xforms.copy
+    },
+    {
+      src: { value: lButton("trigger").pressed },
+      dest: {
+        value: leftTriggerPressed2
+      },
+      xform: xforms.copy
+    },
+    {
+      src: {
+        value: rButton("grip").pressed
+      },
+      dest: {
+        value: rightGripPressed1
+      },
+      xform: xforms.copy
+    },
+    {
+      src: {
+        value: rButton("grip").pressed
+      },
+      dest: {
+        value: rightGripPressed2
+      },
+      xform: xforms.copy
+    },
     {
       src: {
-        value: rButton("grip").touched
+        value: rightGripPressed1
       },
       dest: {
         value: paths.actions.rightHand.middleRingPinky
@@ -138,7 +187,21 @@ export const viveUserBindings = {
       xform: xforms.any
     },
     {
-      src: { value: rButton("trigger").pressed },
+      src: {
+        value: rButton("trigger").pressed
+      },
+      dest: { value: rightTriggerPressed1 },
+      xform: xforms.copy
+    },
+    {
+      src: {
+        value: rButton("trigger").pressed
+      },
+      dest: { value: rightTriggerPressed2 },
+      xform: xforms.copy
+    },
+    {
+      src: { value: rightTriggerPressed1 },
       dest: {
         value: paths.actions.rightHand.index
       },
@@ -181,9 +244,18 @@ export const viveUserBindings = {
         value: lButton("touchpad").pressed
       },
       dest: {
-        value: lTouchpadRising
+        value: leftTouchpadPressed1
       },
-      xform: xforms.rising
+      xform: xforms.copy
+    },
+    {
+      src: {
+        value: lButton("touchpad").pressed
+      },
+      dest: {
+        value: leftTouchpadPressed2
+      },
+      xform: xforms.copy
     },
     {
       src: {
@@ -240,8 +312,7 @@ export const viveUserBindings = {
         value: rSnapRight
       },
       xform: xforms.copyIfTrue,
-      root: rDpadEast,
-      priority: 100
+      priority: 1
     },
     {
       src: { value: paths.device.keyboard.key("e") },
@@ -283,8 +354,7 @@ export const viveUserBindings = {
         value: rSnapLeft
       },
       xform: xforms.copyIfTrue,
-      root: rDpadWest,
-      priority: 100
+      priority: 1
     },
     {
       src: { value: paths.device.keyboard.key("q") },
@@ -323,7 +393,7 @@ export const viveUserBindings = {
     },
     {
       src: {
-        bool: lButton("touchpad").pressed,
+        bool: leftTouchpadPressed2,
         value: lJoyScaled
       },
       dest: { value: lCharacterAcceleration },
@@ -445,11 +515,10 @@ export const viveUserBindings = {
   ],
   [sets.rightHandTeleporting]: [
     {
-      src: { value: rButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: { value: rTriggerStopTeleport },
       xform: xforms.falling,
-      root: rTriggerFalling,
-      priority: 100
+      priority: 1
     },
     {
       src: { value: rButton("touchpad").pressed },
@@ -462,49 +531,62 @@ export const viveUserBindings = {
       xform: xforms.any
     }
   ],
-  [sets.leftHandHoveringOnNothing]: [...nothingHeldLeft],
+  [sets.leftHandHoveringOnNothing]: [
+    {
+      src: { value: leftTriggerPressed2 },
+      dest: { value: paths.actions.leftHand.startTeleport },
+      xform: xforms.rising
+    }
+  ],
 
   [sets.leftHandTeleporting]: [
     {
-      src: { value: lButton("trigger").pressed },
-      dest: { value: paths.actions.leftHand.stopTeleport },
+      src: { value: leftTriggerPressed2 },
+      dest: { value: leftTriggerFallingStopTeleport },
       xform: xforms.falling,
-      root: lTriggerFalling,
-      priority: 100
+      priority: 1
+    },
+    {
+      src: [leftTriggerFallingStopTeleport, leftTouchpadFallingStopTeleport, leftGripFallingWhileHoldingPen],
+      dest: { value: paths.actions.leftHand.stopTeleport },
+      xform: xforms.any
     }
   ],
 
-  [sets.rightHandHoveringOnNothing]: [...nothingHeldRight],
+  [sets.rightHandHoveringOnNothing]: [
+    {
+      src: { value: rightTriggerPressed2 },
+      dest: { value: paths.actions.rightHand.startTeleport },
+      xform: xforms.rising,
+      priority: 1
+    }
+  ],
 
   [sets.cursorHoveringOnNothing]: [],
 
   [sets.cursorHoveringOnUI]: [
     {
-      src: { value: rButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: { value: paths.actions.cursor.grab },
       xform: xforms.rising,
-      root: rTriggerRising,
-      priority: 100
+      priority: 1
     }
   ],
 
   [sets.leftHandHoveringOnInteractable]: [
     {
-      src: { value: lButton("grip").pressed },
+      src: { value: leftGripPressed2 },
       dest: { value: lGripRisingGrab },
-      xform: xforms.rising,
-      root: lGripRising,
-      priority: 200
+      xform: xforms.rising
     },
     {
-      src: { value: lButton("trigger").pressed },
+      src: { value: leftTriggerPressed2 },
       dest: { value: lTriggerRisingGrab },
       xform: xforms.rising,
-      root: lTriggerRising,
-      priority: 200
+      priority: 1
     },
     {
-      src: [lGripRisingGrab, lTriggerRisingGrab],
+      src: [lGripRisingGrab],
       dest: { value: paths.actions.leftHand.grab },
       xform: xforms.any
     }
@@ -512,38 +594,63 @@ export const viveUserBindings = {
 
   [sets.leftHandHoldingInteractable]: [
     {
-      src: { value: lButton("grip").pressed },
+      src: { value: leftGripPressed2 },
       dest: { value: paths.actions.leftHand.drop },
       xform: xforms.falling,
-      root: lGripFalling,
-      priority: 200
+      priority: 1
     }
   ],
 
   [sets.leftHandHoveringOnPen]: [],
   [sets.leftHandHoldingPen]: [
+    {
+      src: { value: leftTouchpadPressed2 },
+      dest: { value: leftTouchpadFallingStopTeleport },
+      xform: xforms.falling,
+      priority: 1
+    },
+    {
+      src: {
+        value: leftTouchpadPressed2
+      },
+      dest: {
+        value: lTouchpadRising1
+      },
+      xform: xforms.rising,
+      priority: 1
+    },
     {
       src: {
-        bool: lTouchpadRising,
+        bool: lTouchpadRising1,
         value: lDpadCenter
       },
       dest: { value: paths.actions.leftHand.startTeleport },
-      xform: xforms.copyIfTrue
+      xform: xforms.copyIfTrue,
+      priority: 1
     },
     {
-      src: { value: lButton("trigger").pressed },
+      src: { value: leftTriggerPressed2 },
       dest: { value: paths.actions.leftHand.startDrawing },
-      xform: xforms.rising
+      xform: xforms.rising,
+      priorty: 2
     },
     {
-      src: { value: lButton("trigger").pressed },
+      src: { value: leftTriggerPressed2 },
       dest: { value: lTriggerFallingStopDrawing },
-      xform: xforms.falling
+      xform: xforms.falling,
+      priorty: 2
     },
     {
-      src: { value: lButton("grip").pressed },
+      src: { value: leftGripPressed2 },
       dest: { value: lGripFallingStopDrawing },
-      xform: xforms.falling
+      xform: xforms.falling,
+      priority: 1
+    },
+    {
+      src: { value: leftGripPressed2 },
+      dest: { value: leftGripFallingWhileHoldingPen },
+      xform: xforms.falling,
+      priority: 1
     },
     {
       src: [lTriggerFallingStopDrawing, lGripFallingStopDrawing],
@@ -552,27 +659,35 @@ export const viveUserBindings = {
     },
     {
       src: {
-        bool: lTouchpadRising,
+        value: leftTouchpadPressed2
+      },
+      dest: {
+        value: lTouchpadRising2
+      },
+      xform: xforms.rising,
+      priority: 1
+    },
+    {
+      src: {
+        bool: lTouchpadRising2,
         value: lDpadNorth
       },
       dest: {
         value: paths.actions.leftHand.penNextColor
       },
       xform: xforms.copyIfTrue,
-      root: lDpadNorth,
-      priority: 200
+      priority: 2
     },
     {
       src: {
-        bool: lTouchpadRising,
+        bool: lTouchpadRising2,
         value: lDpadSouth
       },
       dest: {
         value: paths.actions.leftHand.penPrevColor
       },
       xform: xforms.copyIfTrue,
-      root: lDpadSouth,
-      priority: 200
+      priority: 2
     },
     {
       src: {
@@ -586,21 +701,18 @@ export const viveUserBindings = {
 
   [sets.cursorHoveringOnInteractable]: [
     {
-      src: { value: rButton("grip").pressed },
+      src: { value: rightGripPressed2 },
       dest: { value: rGripRisingGrab },
-      xform: xforms.rising,
-      root: rGripRising,
-      priority: 200
+      xform: xforms.rising
     },
     {
-      src: { value: rButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: { value: rTriggerRisingGrab },
       xform: xforms.rising,
-      root: rTriggerRising,
-      priority: 200
+      priority: 1
     },
     {
-      src: [rGripRisingGrab, rTriggerRisingGrab],
+      src: [rGripRisingGrab],
       dest: { value: paths.actions.cursor.grab },
       xform: xforms.any
     }
@@ -616,31 +728,28 @@ export const viveUserBindings = {
       xform: xforms.touch_axis_scroll(-1)
     },
     {
-      src: { value: rButton("grip").pressed },
+      src: { value: rightGripPressed2 },
       dest: { value: cursorDrop1 },
       xform: xforms.falling,
-      root: rGripFalling,
-      priority: 200
+      priority: 1
     },
     {
-      src: { value: rButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: {
         value: cursorDrop2
       },
       xform: xforms.falling,
-      root: rTriggerFalling,
-      priority: 200
+      priority: 2
     },
     {
-      src: [cursorDrop1, cursorDrop2],
+      src: [cursorDrop1],
       dest: { value: paths.actions.cursor.drop },
       xform: xforms.any
     },
     {
-      src: null,
+      src: {},
       dest: { value: ensureFrozenViaDpad },
       root: rootForFrozenOverrideWhenHolding,
-      priority: 100,
       xform: xforms.always(false)
     }
   ],
@@ -654,19 +763,20 @@ export const viveUserBindings = {
         value: rDpadCenter
       },
       dest: { value: paths.actions.rightHand.startTeleport },
-      xform: xforms.copyIfTrue
+      xform: xforms.copyIfTrue,
+      priority: 2
     },
     {
-      src: { value: rButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: { value: paths.actions.cursor.startDrawing },
-      xform: xforms.rising
+      xform: xforms.rising,
+      priority: 3
     },
     {
-      src: { value: rButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: { value: paths.actions.cursor.stopDrawing },
       xform: xforms.falling,
-      root: rTriggerFalling,
-      priority: 300
+      priority: 3
     },
     {
       src: {
@@ -685,8 +795,7 @@ export const viveUserBindings = {
         value: paths.actions.cursor.penNextColor
       },
       xform: xforms.copyIfTrue,
-      root: rDpadNorth,
-      priority: 200
+      priority: 2
     },
     {
       src: {
@@ -697,28 +806,24 @@ export const viveUserBindings = {
         value: paths.actions.cursor.penPrevColor
       },
       xform: xforms.copyIfTrue,
-      root: rDpadSouth,
-      priority: 200
+      priority: 2
     }
   ],
 
   [sets.rightHandHoveringOnInteractable]: [
     {
-      src: { value: rButton("grip").pressed },
+      src: { value: rightGripPressed2 },
       dest: { value: rGripRisingGrab },
-      xform: xforms.rising,
-      root: rGripRising,
-      priority: 200
+      xform: xforms.rising
     },
     {
-      src: { value: rButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: { value: rTriggerRisingGrab },
       xform: xforms.rising,
-      root: rTriggerRising,
-      priority: 200
+      priority: 2
     },
     {
-      src: [rGripRisingGrab, rTriggerRisingGrab],
+      src: [rGripRisingGrab],
       dest: { value: paths.actions.rightHand.grab },
       xform: xforms.any
     }
@@ -726,31 +831,28 @@ export const viveUserBindings = {
 
   [sets.rightHandHoldingInteractable]: [
     {
-      src: { value: rButton("grip").pressed },
+      src: { value: rightGripPressed2 },
       dest: { value: rHandDrop1 },
       xform: xforms.falling,
-      root: rGripFalling,
-      priority: 200
+      priority: 1
     },
     {
-      src: { value: rButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: {
         value: rHandDrop2
       },
       xform: xforms.falling,
-      root: rTriggerFalling,
-      priority: 200
+      priority: 2
     },
     {
-      src: [rHandDrop1, rHandDrop2],
+      src: [rHandDrop1],
       dest: { value: paths.actions.rightHand.drop },
       xform: xforms.any
     },
     {
-      src: null,
+      src: {},
       dest: { value: ensureFrozenViaDpad },
       root: rootForFrozenOverrideWhenHolding,
-      priority: 100,
       xform: xforms.always(false)
     }
   ],
@@ -765,16 +867,16 @@ export const viveUserBindings = {
       xform: xforms.copyIfTrue
     },
     {
-      src: { value: rButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: { value: paths.actions.rightHand.startDrawing },
-      xform: xforms.rising
+      xform: xforms.rising,
+      priority: 3
     },
     {
-      src: { value: rButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: { value: paths.actions.rightHand.stopDrawing },
       xform: xforms.falling,
-      root: rTriggerFalling,
-      priority: 300
+      priority: 3
     },
     {
       src: {
@@ -785,8 +887,7 @@ export const viveUserBindings = {
         value: paths.actions.rightHand.penNextColor
       },
       xform: xforms.copyIfTrue,
-      root: rDpadNorth,
-      priority: 200
+      priority: 2
     },
     {
       src: {
@@ -797,8 +898,7 @@ export const viveUserBindings = {
         value: paths.actions.rightHand.penPrevColor
       },
       xform: xforms.copyIfTrue,
-      root: rDpadSouth,
-      priority: 200
+      priority: 2
     },
     {
       src: {
@@ -816,16 +916,16 @@ export const viveUserBindings = {
 
   [sets.rightHandHoldingCamera]: [
     {
-      src: { value: rButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: { value: paths.actions.rightHand.takeSnapshot },
-      xform: xforms.rising
+      xform: xforms.rising,
+      priority: 3
     },
     {
-      src: { value: rButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: { value: paths.noop },
       xform: xforms.falling,
-      root: rTriggerFalling,
-      priority: 400
+      priority: 3
     }
   ],
   [sets.leftHandHoldingCamera]: [
@@ -837,28 +937,24 @@ export const viveUserBindings = {
   ],
   [sets.cursorHoldingCamera]: [
     {
-      src: { value: rButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: { value: paths.actions.cursor.takeSnapshot },
-      xform: xforms.rising
+      xform: xforms.rising,
+      priority: 3
     },
     {
-      src: { value: rButton("trigger").pressed },
+      src: { value: rightTriggerPressed2 },
       dest: { value: paths.noop },
       xform: xforms.falling,
-      root: rTriggerFalling,
-      priority: 400
+      priority: 3
     }
   ],
-  [sets.globalPost]: [
-    {
-      src: [ensureFrozenViaDpad, ensureFrozenViaKeyboard],
-      dest: { value: paths.actions.ensureFrozen },
-      xform: xforms.any
-    },
+  [sets.inputFocused]: [
     {
-      src: [thawViaDpad, thawViaKeyboard],
-      dest: { value: paths.actions.thaw },
-      xform: xforms.any
+      src: { value: "/device/keyboard" },
+      dest: { value: paths.noop },
+      xform: xforms.noop,
+      priority: 1000
     }
   ]
-};
+});
diff --git a/src/systems/userinput/bindings/xbox-controller-user.js b/src/systems/userinput/bindings/xbox-controller-user.js
index b9d230a799ca5515615abc24318ac9507e779b6d..67699c5395cb2bdeab569b1a6b6ac1d4ff43d435 100644
--- a/src/systems/userinput/bindings/xbox-controller-user.js
+++ b/src/systems/userinput/bindings/xbox-controller-user.js
@@ -1,6 +1,7 @@
 import { paths } from "../paths";
 import { sets } from "../sets";
 import { xforms } from "./xforms";
+import { addSetsToBindings } from "./utils";
 
 const xboxUnscaledCursorScalePenTip = "foobarbazbotbooch";
 
@@ -8,7 +9,7 @@ const button = paths.device.xbox.button;
 const axis = paths.device.xbox.axis;
 const rightTriggerFalling = "/vars/xbox/rightTriggerFalling";
 
-export const xboxControllerUserBindings = {
+export const xboxControllerUserBindings = addSetsToBindings({
   [sets.cursorHoldingInteractable]: [
     {
       src: { value: button("rightTrigger").pressed },
@@ -187,4 +188,4 @@ export const xboxControllerUserBindings = {
       priority: 100
     }
   ]
-};
+});
diff --git a/src/systems/userinput/devices/app-aware-mouse.js b/src/systems/userinput/devices/app-aware-mouse.js
index de5f4acecdfe6ca4ba0400878f078c770c9c455a..837288f7cc3f1768a87ca34fc51f8b122adb410e 100644
--- a/src/systems/userinput/devices/app-aware-mouse.js
+++ b/src/systems/userinput/devices/app-aware-mouse.js
@@ -2,10 +2,7 @@ import { sets } from "../sets";
 import { paths } from "../paths";
 import { Pose } from "../pose";
 
-const calculateCursorPose = function(camera, coords) {
-  const cursorPose = new Pose();
-  const origin = new THREE.Vector3();
-  const direction = new THREE.Vector3();
+const calculateCursorPose = function(camera, coords, origin, direction, cursorPose) {
   origin.setFromMatrixPosition(camera.matrixWorld);
   direction
     .set(coords[0], coords[1], 0.5)
@@ -20,9 +17,19 @@ export class AppAwareMouseDevice {
   constructor() {
     this.prevButtonLeft = false;
     this.clickedOnAnything = false;
+    this.cursorPose = new Pose();
+    this.prevCursorPose = new Pose();
+    this.origin = new THREE.Vector3();
+    this.prevOrigin = new THREE.Vector3();
+    this.direction = new THREE.Vector3();
+    this.prevDirection = new THREE.Vector3();
   }
 
   write(frame) {
+    this.prevCursorPose.copy(this.cursorPose);
+    this.prevOrigin.copy(this.origin);
+    this.prevDirection.copy(this.prevDirection);
+
     if (!this.cursorController) {
       this.cursorController = document.querySelector("[cursor-controller]").components["cursor-controller"];
     }
@@ -32,6 +39,7 @@ export class AppAwareMouseDevice {
     }
 
     const buttonLeft = frame[paths.device.mouse.buttonLeft];
+    const buttonRight = frame[paths.device.mouse.buttonRight];
     if (buttonLeft && !this.prevButtonLeft) {
       const rawIntersections = [];
       this.cursorController.raycaster.intersectObjects(this.cursorController.targets, true, rawIntersections);
@@ -50,11 +58,17 @@ export class AppAwareMouseDevice {
       this.clickedOnAnything = false;
     }
 
-    if (!this.clickedOnAnything && buttonLeft) {
+    if ((!this.clickedOnAnything && buttonLeft) || buttonRight) {
       frame[paths.device.smartMouse.cameraDelta] = frame[paths.device.mouse.movementXY];
     }
 
     const coords = frame[paths.device.mouse.coords];
-    frame[paths.device.smartMouse.cursorPose] = calculateCursorPose(this.camera, coords);
+    frame[paths.device.smartMouse.cursorPose] = calculateCursorPose(
+      this.camera,
+      coords,
+      this.origin,
+      this.direction,
+      this.cursorPose
+    );
   }
 }
diff --git a/src/systems/userinput/devices/oculus-touch-controller.js b/src/systems/userinput/devices/oculus-touch-controller.js
index 75658748c22e7aadef2832867ceefd1871758a93..207d5af2284a11b79dc50d0c97f87f594838dfb9 100644
--- a/src/systems/userinput/devices/oculus-touch-controller.js
+++ b/src/systems/userinput/devices/oculus-touch-controller.js
@@ -44,15 +44,17 @@ export class OculusTouchControllerDevice {
   write(frame) {
     if (!this.gamepad.connected) return;
 
-    this.gamepad.buttons.forEach((button, i) => {
+    for (let i = 0; i < this.gamepad.buttons.length; i++) {
       const buttonPath = paths.device.gamepad(this.gamepad.index).button(i);
+      const button = this.gamepad.buttons[i];
       frame[buttonPath.pressed] = !!button.pressed;
       frame[buttonPath.touched] = !!button.touched;
       frame[buttonPath.value] = button.value;
-    });
-    this.gamepad.axes.forEach((axis, i) => {
+    }
+    for (let i = 0; i < this.gamepad.axes.length; i++) {
+      const axis = this.gamepad.axes[i];
       frame[paths.device.gamepad(this.gamepad.index).axis(i)] = axis;
-    });
+    }
 
     this.buttonMap.forEach(button => {
       const outpath = this.path.button(button.name);
@@ -64,10 +66,10 @@ export class OculusTouchControllerDevice {
       frame[this.path.axis(axis.name)] = frame[paths.device.gamepad(this.gamepad.index).axis(axis.axisId)];
     });
 
-    const rayObject = document.querySelector(this.selector).object3D;
-    rayObject.updateMatrixWorld();
-    this.rayObjectRotation.setFromRotationMatrix(rayObject.matrixWorld);
-    this.pose.position.setFromMatrixPosition(rayObject.matrixWorld);
+    this.rayObject = this.rayObject || document.querySelector(this.selector).object3D;
+    this.rayObject.updateMatrixWorld();
+    this.rayObjectRotation.setFromRotationMatrix(this.rayObject.matrixWorld);
+    this.pose.position.setFromMatrixPosition(this.rayObject.matrixWorld);
     this.pose.direction.set(0, 0, -1).applyQuaternion(this.rayObjectRotation);
     this.pose.fromOriginAndDirection(this.pose.position, this.pose.direction);
     frame[this.path.pose] = this.pose;
diff --git a/src/systems/userinput/paths.js b/src/systems/userinput/paths.js
index 7f38c58cbefcd40e9f9b08c0e47114f6ebab3ee2..d29de98850865fcad38a24dc911553a48e8268a1 100644
--- a/src/systems/userinput/paths.js
+++ b/src/systems/userinput/paths.js
@@ -1,7 +1,6 @@
 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";
diff --git a/src/systems/userinput/pose.js b/src/systems/userinput/pose.js
index 3a75b14203adc7c6acc23a7c3efca8c1001eaa58..31db1799940b656ce125ce9a05730065050a004e 100644
--- a/src/systems/userinput/pose.js
+++ b/src/systems/userinput/pose.js
@@ -19,6 +19,11 @@ export function Pose() {
         .normalize();
       this.fromOriginAndDirection(this.position, this.direction);
       return this;
+    },
+    copy: function(pose) {
+      this.position.copy(pose.position);
+      this.direction.copy(pose.direction);
+      this.orientation.copy(pose.orientation);
     }
   };
 }
diff --git a/src/systems/userinput/resolve-action-sets.js b/src/systems/userinput/resolve-action-sets.js
index 4341064a1085ba55f3584d4a3a5e00bcd5a8d6d7..4d6e9236c27ec57cb8b47848534b8177229763a4 100644
--- a/src/systems/userinput/resolve-action-sets.js
+++ b/src/systems/userinput/resolve-action-sets.js
@@ -1,12 +1,20 @@
 import { sets } from "./sets";
 
-export function updateActionSetsBasedOnSuperhands() {
-  const rightHandState = document.querySelector("#player-right-controller").components["super-hands"].state;
-  const leftHandState = document.querySelector("#player-left-controller").components["super-hands"].state;
-  const cursorHand = document.querySelector("#cursor").components["super-hands"].state;
-  const leftTeleporter = document.querySelector("#player-left-controller").components["teleport-controls"];
-  const rightTeleporter = document.querySelector("#player-right-controller").components["teleport-controls"];
-  const cursorController = document.querySelector("#cursor-controller").components["cursor-controller"];
+let rightHandState;
+let leftHandState;
+let cursorHand;
+let leftTeleporter;
+let rightTeleporter;
+let cursorController;
+
+export function resolveActionSets() {
+  rightHandState = rightHandState || document.querySelector("#player-right-controller").components["super-hands"].state;
+  leftHandState = leftHandState || document.querySelector("#player-left-controller").components["super-hands"].state;
+  cursorHand = cursorHand || document.querySelector("#cursor").components["super-hands"].state;
+  leftTeleporter = leftTeleporter || document.querySelector("#player-left-controller").components["teleport-controls"];
+  rightTeleporter =
+    rightTeleporter || document.querySelector("#player-right-controller").components["teleport-controls"];
+  cursorController = cursorController || document.querySelector("#cursor-controller").components["cursor-controller"];
 
   const leftHandHoveringOnInteractable =
     !leftTeleporter.active &&
@@ -21,17 +29,10 @@ export function updateActionSetsBasedOnSuperhands() {
     leftHandState.has("hover-start") &&
     leftHandState.get("hover-start").matches(".icamera, .icamera *");
   const leftHandHoldingInteractable =
-    !leftTeleporter.active &&
-    leftHandState.has("grab-start") &&
-    leftHandState.get("grab-start").matches(".interactable, .interactable *");
-  const leftHandHoldingPen =
-    !leftTeleporter.active &&
-    leftHandState.has("grab-start") &&
-    leftHandState.get("grab-start").matches(".pen, .pen *");
+    leftHandState.has("grab-start") && leftHandState.get("grab-start").matches(".interactable, .interactable *");
+  const leftHandHoldingPen = leftHandState.has("grab-start") && leftHandState.get("grab-start").matches(".pen, .pen *");
   const leftHandHoldingCamera =
-    !leftTeleporter.active &&
-    leftHandState.has("grab-start") &&
-    leftHandState.get("grab-start").matches(".icamera, .icamera *");
+    leftHandState.has("grab-start") && leftHandState.get("grab-start").matches(".icamera, .icamera *");
   const leftHandHovering = !leftTeleporter.active && leftHandState.has("hover-start");
   const leftHandHoveringOnNothing = !leftHandHovering && !leftHandState.has("grab-start");
   const leftHandTeleporting = leftTeleporter.active;
@@ -58,17 +59,12 @@ export function updateActionSetsBasedOnSuperhands() {
     rightHandState.has("hover-start") &&
     rightHandState.get("hover-start").matches(".icamera, .icamera *");
   const rightHandHoldingInteractable =
-    !rightHandTeleporting &&
     !cursorGrabbing &&
     rightHandState.has("grab-start") &&
     rightHandState.get("grab-start").matches(".interactable, .interactable *");
   const rightHandHoldingPen =
-    !rightHandTeleporting &&
-    !cursorGrabbing &&
-    rightHandState.has("grab-start") &&
-    rightHandState.get("grab-start").matches(".pen, .pen *");
+    !cursorGrabbing && rightHandState.has("grab-start") && rightHandState.get("grab-start").matches(".pen, .pen *");
   const rightHandHoldingCamera =
-    !rightTeleporter.active &&
     !cursorGrabbing &&
     rightHandState.has("grab-start") &&
     rightHandState.get("grab-start").matches(".icamera, .icamera *");
@@ -83,11 +79,18 @@ export function updateActionSetsBasedOnSuperhands() {
   // Cursor
   cursorController.enabled = !(rightHandTeleporting || rightHandHovering || rightHandGrabbing);
 
+  const cursorHoveringOnUI =
+    cursorController.enabled &&
+    !rightHandTeleporting &&
+    !rightHandHovering &&
+    !rightHandGrabbing &&
+    (cursorHand.has("hover-start") && cursorHand.get("hover-start").matches(".ui, .ui *"));
   const cursorHoveringOnInteractable =
     cursorController.enabled &&
     !rightHandTeleporting &&
     !rightHandHovering &&
     !rightHandGrabbing &&
+    !cursorHoveringOnUI &&
     cursorHand.has("hover-start") &&
     cursorHand.get("hover-start").matches(".interactable, .interactable *");
   const cursorHoveringOnCamera =
@@ -95,39 +98,29 @@ export function updateActionSetsBasedOnSuperhands() {
     !rightTeleporter.active &&
     !rightHandHovering &&
     !rightHandGrabbing &&
+    !cursorHoveringOnUI &&
     (cursorHand.has("hover-start") && cursorHand.get("hover-start").matches(".icamera, .icamera *"));
-  const cursorHoveringOnUI =
-    cursorController.enabled &&
-    !rightHandTeleporting &&
-    !rightHandHovering &&
-    !rightHandGrabbing &&
-    (cursorHand.has("hover-start") && cursorHand.get("hover-start").matches(".ui, .ui *"));
   const cursorHoveringOnPen =
     cursorController.enabled &&
     !rightHandTeleporting &&
     !rightHandHovering &&
     !rightHandGrabbing &&
+    !cursorHoveringOnUI &&
     cursorHand.has("hover-start") &&
     cursorHand.get("hover-start").matches(".pen, .pen *");
   const cursorHoldingInteractable =
-    cursorController.enabled &&
     !rightHandTeleporting &&
     cursorHand.has("grab-start") &&
     cursorHand.get("grab-start").matches(".interactable, .interactable *");
   const cursorHoldingPen =
-    cursorController.enabled &&
-    !rightHandTeleporting &&
-    cursorHand.has("grab-start") &&
-    cursorHand.get("grab-start").matches(".pen, .pen *");
+    !rightHandTeleporting && cursorHand.has("grab-start") && cursorHand.get("grab-start").matches(".pen, .pen *");
 
   const cursorHoldingCamera =
-    cursorController.enabled &&
     !rightTeleporter.active &&
     cursorHand.has("grab-start") &&
     cursorHand.get("grab-start").matches(".icamera, .icamera *");
 
   const cursorHoveringOnNothing =
-    cursorController.enabled &&
     !rightHandTeleporting &&
     !rightHandHovering &&
     !rightHandGrabbing &&
@@ -162,4 +155,8 @@ export function updateActionSetsBasedOnSuperhands() {
   userinput.toggleSet(sets.cursorHoldingPen, cursorHoldingPen);
   userinput.toggleSet(sets.cursorHoldingCamera, cursorHoldingCamera);
   userinput.toggleSet(sets.cursorHoldingInteractable, cursorHoldingInteractable);
+  userinput.toggleSet(
+    sets.inputFocused,
+    document.activeElement.nodeName === "INPUT" || document.activeElement.nodeName === "TEXTAREA"
+  );
 }
diff --git a/src/systems/userinput/sets.js b/src/systems/userinput/sets.js
index d49df407a637d800f3bf56cde3600d7a24a36c95..e29d0489e0d091edf40ec8746d782237b3fa263c 100644
--- a/src/systems/userinput/sets.js
+++ b/src/systems/userinput/sets.js
@@ -1,5 +1,6 @@
 export const sets = {};
 sets.global = "global";
+sets.inputFocused = "inputFocused";
 sets.cursorHoveringOnPen = "cursorHoveringOnPen";
 sets.cursorHoveringOnCamera = "cursorHoveringOnCamera";
 sets.cursorHoveringOnInteractable = "cursorHoveringOnInteractable";
@@ -24,4 +25,3 @@ sets.leftHandHoldingPen = "leftHandHoldingPen";
 sets.leftHandHoldingCamera = "leftHandHoldingCamera";
 sets.leftHandHoldingInteractable = "leftHandHoldingInteractable";
 sets.leftHandHoveringOnNothing = "leftHandHoveringOnNothing";
-sets.globalPost = "globalPost";
diff --git a/src/systems/userinput/userinput-debug.js b/src/systems/userinput/userinput-debug.js
new file mode 100644
index 0000000000000000000000000000000000000000..967d2d6af37e97308c7be322a18d2b58ffeaa23a
--- /dev/null
+++ b/src/systems/userinput/userinput-debug.js
@@ -0,0 +1,52 @@
+import { paths } from "./paths";
+AFRAME.registerSystem("userinput-debug", {
+  active: true,
+
+  tick() {
+    if (!this.active) {
+      return;
+    }
+    const userinput = AFRAME.scenes[0].systems.userinput;
+    if (userinput.get(paths.actions.logDebugFrame)) {
+      console.log(userinput);
+      console.log("sorted", userinput.sortedBindings);
+      console.log("actives", userinput.actives);
+      console.log("masks", userinput.masks);
+      console.log("masked", userinput.masked);
+      console.log("devices", userinput.activeDevices);
+      console.log("activeSets", userinput.activeSets);
+      console.log("frame", userinput.frame);
+      console.log("xformStates", userinput.xformStates);
+      const { sortedBindings, actives, masked } = userinput;
+      for (const i in sortedBindings) {
+        const sb = [];
+        if (masked[i].length > 0) {
+          for (const j of masked[i]) {
+            sb.push(JSON.stringify(sortedBindings[j]));
+          }
+        }
+
+        if (this.logBindings) {
+          console.log(
+            "binding: ",
+            i,
+            "\n",
+            sortedBindings[i],
+            "\n",
+            "dest: ",
+            sortedBindings[i].dest && Object.values(sortedBindings[i].dest),
+            "\n",
+            "active: ",
+            actives[i],
+            "\n",
+            "maskedBy: ",
+            masked[i],
+            "\n",
+            sb.join("\n"),
+            "\n"
+          );
+        }
+      }
+    }
+  }
+});
diff --git a/src/systems/userinput/userinput.js b/src/systems/userinput/userinput.js
index 58136575df7246432debf21049b1c7e78b715603..dc94688c7e000a10bded3c5acc6b18e4eae9e661 100644
--- a/src/systems/userinput/userinput.js
+++ b/src/systems/userinput/userinput.js
@@ -1,4 +1,3 @@
-import { paths } from "./paths";
 import { sets } from "./sets";
 
 import { MouseDevice } from "./devices/mouse";
@@ -22,36 +21,128 @@ import { viveUserBindings } from "./bindings/vive-user";
 import { xboxControllerUserBindings } from "./bindings/xbox-controller-user";
 import { daydreamUserBindings } from "./bindings/daydream-user";
 
-import { updateActionSetsBasedOnSuperhands } from "./resolve-action-sets";
+import { resolveActionSets } from "./resolve-action-sets";
 import { GamepadDevice } from "./devices/gamepad";
 import { gamepadBindings } from "./bindings/generic-gamepad";
 
-const priorityMap = new Map();
-function prioritizeBindings(registeredMappings, activeSets) {
-  const activeBindings = new Set();
-  priorityMap.clear();
-  for (const mapping of registeredMappings) {
+function intersection(setA, setB) {
+  const _intersection = new Set();
+  for (const elem of setB) {
+    if (setA.has(elem)) {
+      _intersection.add(elem);
+    }
+  }
+  return _intersection;
+}
+
+const satisfiesPath = (binding, path) => {
+  for (const key in binding.dest) {
+    if (binding.dest[key].indexOf(path) !== -1) {
+      return true;
+    }
+  }
+  return false;
+};
+
+const satisfyPath = (bindings, path) => {
+  for (const binding of bindings) {
+    if (satisfiesPath(binding, path)) {
+      return true;
+    }
+  }
+  return false;
+};
+
+const satisfiedBy = (binding, bindings) => {
+  for (const path of Object.values(binding.src)) {
+    if (path.startsWith("/device/")) continue;
+    if (!satisfyPath(bindings, path)) return false;
+  }
+  return true;
+};
+
+function dependencySort(mappings) {
+  const unsorted = [];
+  for (const mapping of mappings) {
     for (const setName in mapping) {
-      if (!activeSets.has(setName) || !mapping[setName]) continue;
       for (const binding of mapping[setName]) {
-        const { root, priority } = binding;
-        const prevBinding = priorityMap.get(root);
-        if (!root || !priority) {
-          activeBindings.add(binding);
-        } else if (!prevBinding) {
-          activeBindings.add(binding);
-          priorityMap.set(root, binding);
-        } else if (priority > prevBinding.priority) {
-          activeBindings.delete(priorityMap.get(root));
-          activeBindings.add(binding);
-          priorityMap.set(root, binding);
-        } else if (prevBinding.priority === priority) {
-          console.error("equal priorities on same root", binding, priorityMap.get(root));
-        }
+        unsorted.push(binding);
       }
     }
   }
-  return activeBindings;
+
+  const sorted = [];
+  while (unsorted.length > 0) {
+    const binding = unsorted.shift();
+    if (satisfiedBy(binding, sorted)) {
+      sorted.push(binding);
+    } else {
+      unsorted.push(binding);
+    }
+  }
+
+  return sorted;
+}
+
+function canMask(masker, masked) {
+  if (masker.priority === undefined) {
+    masker.priority = 0;
+  }
+  if (masked.priority === undefined) {
+    masked.priority = 0;
+  }
+  if (masked.priority >= masker.priority) return false;
+  for (const maskerKey in masker.src) {
+    const maskerPath = masker.src[maskerKey];
+    for (const maskedKey in masked.src) {
+      const maskedPath = masked.src[maskedKey];
+      if (maskedPath.indexOf(maskerPath) !== -1) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+function computeMasks(bindings) {
+  const masks = [];
+  for (let row = 0; row < bindings.length; row++) {
+    for (let col = 0; col < bindings.length; col++) {
+      masks[row] = masks[row] || [];
+      if (canMask(bindings[col], bindings[row])) {
+        masks[row].push(col);
+      }
+    }
+  }
+  return masks;
+}
+
+function isActive(binding, sets) {
+  for (let i = 0; i < binding.sets.length; i++) {
+    if (sets.has(binding.sets[i])) {
+      return true;
+    }
+  }
+  return false;
+}
+
+function computeExecutionStrategy(sortedBindings, masks, activeSets) {
+  const actives = [];
+  for (let row = 0; row < sortedBindings.length; row++) {
+    actives[row] = isActive(sortedBindings[row], activeSets);
+  }
+
+  const masked = [];
+  for (let row = 0; row < sortedBindings.length; row++) {
+    for (let col = 0; col < sortedBindings.length; col++) {
+      masked[row] = masked[row] || [];
+      if (masks[row].indexOf(col) !== -1 && isActive(sortedBindings[col], activeSets)) {
+        masked[row].push(col);
+      }
+    }
+  }
+
+  return { actives, masked };
 }
 
 AFRAME.registerSystem("userinput", {
@@ -66,8 +157,10 @@ AFRAME.registerSystem("userinput", {
   init() {
     this.frame = {};
 
-    this.activeSets = new Set([sets.global, sets.globalPost]);
+    this.prevActiveSets = new Set();
+    this.activeSets = new Set([sets.global]);
     this.pendingSetChanges = [];
+    this.xformStates = new Map();
     this.activeDevices = new Set([new MouseDevice(), new AppAwareMouseDevice(), new KeyboardDevice(), new HudDevice()]);
 
     if (AFRAME.utils.device.isMobile()) {
@@ -75,7 +168,7 @@ AFRAME.registerSystem("userinput", {
     }
 
     this.registeredMappings = new Set([keyboardDebuggingBindings]);
-    this.xformStates = new Map();
+    this.registeredMappingsChanged = true;
 
     const vrGamepadMappings = new Map();
     vrGamepadMappings.set(ViveControllerDevice, viveUserBindings);
@@ -112,6 +205,8 @@ AFRAME.registerSystem("userinput", {
         const mapping = nonVRGamepadMappings.get(activeDevice.constructor);
         mapping && this.registeredMappings.add(mapping);
       }
+
+      this.registeredMappingsChanged = true;
     };
 
     const gamepadConnected = e => {
@@ -168,21 +263,52 @@ AFRAME.registerSystem("userinput", {
   },
 
   tick() {
-    updateActionSetsBasedOnSuperhands();
+    const registeredMappingsChanged = this.registeredMappingsChanged;
+    if (registeredMappingsChanged) {
+      this.registeredMappingsChanged = false;
+      this.prevSortedBindings = this.sortedBindings;
+      this.sortedBindings = dependencySort(this.registeredMappings);
+      if (!this.prevSortedBindings) {
+        this.prevSortedBindings = this.sortedBindings;
+      }
+      this.masks = computeMasks(this.sortedBindings);
+    }
 
+    this.prevActiveSets.clear();
+    for (const item of this.activeSets) {
+      this.prevActiveSets.add(item);
+    }
+    resolveActionSets();
     for (const { set, value } of this.pendingSetChanges) {
       this.activeSets[value ? "add" : "delete"](set);
     }
+    const activeSetsChanged =
+      this.prevActiveSets.size !== this.activeSets.size ||
+      intersection(this.prevActiveSets, this.activeSets).size !== this.activeSets.size;
     this.pendingSetChanges.length = 0;
+    if (registeredMappingsChanged || activeSetsChanged || (!this.actives && !this.masked)) {
+      this.prevActives = this.actives;
+      this.prevMasked = this.masked;
+      const { actives, masked } = computeExecutionStrategy(this.sortedBindings, this.masks, this.activeSets);
+      this.actives = actives;
+      this.masked = masked;
+    }
 
     this.frame = {};
     for (const device of this.activeDevices) {
       device.write(this.frame);
     }
 
-    const activeBindings = prioritizeBindings(this.registeredMappings, this.activeSets);
-    for (const binding of activeBindings) {
-      const bindingExistedLastFrame = this.activeBindings && this.activeBindings.has(binding);
+    for (let i = 0; i < this.sortedBindings.length; i++) {
+      if (!this.actives[i] || this.masked[i].length > 0) continue;
+
+      const binding = this.sortedBindings[i];
+
+      let bindingExistedLastFrame = true;
+      if (!registeredMappingsChanged && activeSetsChanged && this.prevSortedBindings) {
+        const j = this.prevSortedBindings.indexOf(binding);
+        bindingExistedLastFrame = j > -1 && this.prevActives[j] && this.prevMasked[j].length === 0;
+      }
       if (!bindingExistedLastFrame) {
         this.xformStates.delete(binding);
       }
@@ -194,15 +320,7 @@ AFRAME.registerSystem("userinput", {
       }
     }
 
-    this.activeBindings = activeBindings;
-
-    if (this.frame[paths.actions.logDebugFrame] || this.frame[paths.actions.log]) {
-      console.log("frame", this.frame);
-      console.log("sets", this.activeSets);
-      console.log("bindings", this.activeBindings);
-      console.log("mappings", this.registeredMappings);
-      console.log("devices", this.activeDevices);
-      console.log("xformStates", this.xformStates);
-    }
+    this.prevSortedBindings = this.sortedBindings;
+    this.prevFrame = this.frame;
   }
 });