diff --git a/package.json b/package.json
index 7acb2082dd06f7d4fc69c4d8d400414767758355..ed63ff2bb1d5a12534474931b83f9befa9d335b7 100644
--- a/package.json
+++ b/package.json
@@ -6,10 +6,10 @@
   "scripts": {
     "dev": "webpack-dev-server --https --open --config webpack.dev.js",
     "build": "webpack --config webpack.prod.js",
-    "prettier": "prettier --write src/**/*.js",
-    "deploy": "npm run build && now deploy --static ./public/"
+    "prettier": "prettier --write src/**/*.js"
   },
   "dependencies": {
+    "aframe-extras": "^3.12.4",
     "aframe": "0.7.0",
     "aframe-input-mapping-component": "https://github.com/fernandojsg/aframe-input-mapping-component#6ebc38f",
     "aframe-teleport-controls": "https://github.com/netpro2k/aframe-teleport-controls#feature/teleport-origin",
@@ -17,7 +17,6 @@
     "naf-janus-adapter": "^0.1.5",
     "networked-aframe": "https://github.com/netpro2k/networked-aframe#bugfix/chrome/audio",
     "nipplejs": "^0.6.7",
-    "pleasejs": "^0.4.2",
     "query-string": "^5.0.1",
     "react": "^16.1.1",
     "react-dom": "^16.1.1"
diff --git a/public/assets/avatars/Bot_Body_Mesh.glb b/public/assets/avatars/Bot_Body_Mesh.glb
new file mode 100644
index 0000000000000000000000000000000000000000..5b6ec544498ba30094dfb229de1fd4e93fff3da0
Binary files /dev/null and b/public/assets/avatars/Bot_Body_Mesh.glb differ
diff --git a/public/assets/avatars/Bot_Head_Mesh.glb b/public/assets/avatars/Bot_Head_Mesh.glb
new file mode 100644
index 0000000000000000000000000000000000000000..9d4ace94f63794cb1b54119c86d1632d139f2d9f
Binary files /dev/null and b/public/assets/avatars/Bot_Head_Mesh.glb differ
diff --git a/public/assets/avatars/Bot_LeftHand_Mesh.glb b/public/assets/avatars/Bot_LeftHand_Mesh.glb
new file mode 100644
index 0000000000000000000000000000000000000000..1ba4463d4aba8054a20fee420d82962daf071178
Binary files /dev/null and b/public/assets/avatars/Bot_LeftHand_Mesh.glb differ
diff --git a/public/assets/avatars/Bot_RightHand_Mesh.glb b/public/assets/avatars/Bot_RightHand_Mesh.glb
new file mode 100644
index 0000000000000000000000000000000000000000..bb73909d7ed484a241737c116126e278cb58274c
Binary files /dev/null and b/public/assets/avatars/Bot_RightHand_Mesh.glb differ
diff --git a/public/room.html b/public/room.html
index a8c44617f575e7e8c943b2038ef6f603495395e8..481f6ceb1242f8ba7e6bb3195f5c03fc32091f49 100644
--- a/public/room.html
+++ b/public/room.html
@@ -4,18 +4,18 @@
     <title>Mozilla Mixed Reality Social Client</title>
     <script src="./app.bundle.js"></script>
     <style>
-     .a-enter-vr {
-         top: 90px;
-         bottom: auto;
-     }
-     #loader {
-        position: fixed;
-        width: 100vw;
-        height: 100vh;
-        z-index: 10001;
-        background: #eaeaea no-repeat url(assets/loading.gif) center center;
-        opacity: 0.9;
-     }
+        .a-enter-vr {
+            top: 90px;
+            bottom: auto;
+        }
+        #loader {
+            position: fixed;
+            width: 100vw;
+            height: 100vh;
+            z-index: 10001;
+            background: #eaeaea no-repeat url(assets/loading.gif) center center;
+            opacity: 0.9;
+        }
     </style>
 </head>
 
@@ -33,7 +33,10 @@
             <img id="grid" src="assets/grid.png" crossorigin="anonymous" />
             <img id="sky" src="https://cdn.aframe.io/360-image-gallery-boilerplate/img/sechelt.jpg" crossorigin="anonymous" />
 
-            <a-asset-item id="dodec-avatar-head" src="assets/avatars/dodec/DodecAvatarGLTF/DodecAvatar_Head.gltf"></a-asset-item>
+            <a-asset-item id="bot-head-mesh" src="assets/avatars/Bot_Head_Mesh.glb"></a-asset-item>
+            <a-asset-item id="bot-body-mesh" src="assets/avatars/Bot_Body_mesh.glb"></a-asset-item>
+            <a-asset-item id="bot-left-hand-mesh" src="assets/avatars/Bot_LeftHand_Mesh.glb"></a-asset-item>
+            <a-asset-item id="bot-right-hand-mesh" src="assets/avatars/Bot_RightHand_Mesh.glb"></a-asset-item>
 
             <a-asset-item id="watch-model" src="assets/hud/watch.gltf"></a-asset-item>
 
@@ -43,51 +46,132 @@
             <script id="head-template" type="text/html">
                 <a-entity
                     class="head"
-                    gltf-model="#dodec-avatar-head"
+                    gltf-model="#bot-head-mesh"
                     networked-audio-source
                     networked-audio-analyser
-                    matcolor-audio-feedback="objectName: DodecAvatar_Head_0"
+                    matcolor-audio-feedback="objectName: Head_Mesh"
                     scale-audio-feedback
-                    avatar-customization
                     personal-space-invader
+                    rotation="0 180 0"
+                    animation-mixer
                 ></a-entity>
             </script>
 
-            <script id="hand-template" type="text/html">
-                <a-box class="hand" personal-space-invader scale="0.2 0.1 0.3"></a-box>
+            <script id="body-template" type="text/html">
+                <a-entity
+                    class="body"
+                    gltf-model="#bot-body-mesh"
+                    personal-space-invader
+                    rotation="0 180 0"
+                    position="0 -0.05 0"
+                ></a-entity>
+            </script>
+
+            <script id="left-hand-template" type="text/html">
+                <a-entity
+                    class="hand"
+                    gltf-model="#bot-left-hand-mesh"
+                    animation-mixer
+                    personal-space-invader
+                    rotation="-90 90 0"
+                    position="0 0 0.075"
+                ></a-entity>
+            </script>
+
+            <script id="right-hand-template" type="text/html">
+                <a-entity
+                    class="hand"
+                    gltf-model="#bot-right-hand-mesh"
+                    personal-space-invader
+                    rotation="-90 -90 0"
+                    position="0 0 0.075"
+                ></a-entity>
             </script>
 
             <script id="nametag-template" type="text/html">
                 <a-entity
                     class="nametag"
                     nametag-transform="follow: .head"
-                    text="side:double;align:center;color:#555"
+                    text="side: double; align: center; color: #555"
                     position="0 2.5 0"
-                    scale="6 6 6"></a-entity>
+                    scale="6 6 6"
+                ></a-entity>
             </script>
         </a-assets>
-        <a-entity id="player-rig" networked character-controller="pivot: #head">
+
+        <!-- Player Rig -->
+        <a-entity
+            id="player-rig"
+            networked
+            character-controller="pivot: #head"
+        >
             <a-sphere scale="0.1 0.1 0.1"></a-sphere>
-            <a-entity id="head" camera="userHeight: 1.6" personal-space-bubble look-controls networked="template:#head-template;showLocalTemplate:false;"></a-entity>
-            <a-entity id="nametag" networked="template:#nametag-template;showLocalTemplate:false;"></a-entity>
-
-            <a-entity id="left-hand" split-axis-events hand-controls="left" hand-controls-visibility axis-dpad="centerZone: 1" networked="template:#hand-template;showLocalTemplate:false;">
-                <a-entity id="watch" gltf-model="assets/hud/watch.gltf" position="0 0.0015 0.147" rotation="3.5 0 0">
-                    <a-circle mute-state-indicator scale-audio-feedback="analyserSrc: #head; minScale: 0.035; maxScale: 0.08;" position="0 0.023 0"
-                              rotation="-90 0 0" scale="0.04 0.04 0.04" material="color:#d8eece;shader:flat">
-                    </a-circle>
+
+            <a-entity
+                id="head"
+                camera="userHeight: 1.6"
+                personal-space-bubble
+                look-controls
+                networked="template: #head-template; showLocalTemplate: false;" 
+            ></a-entity>
+
+            <a-entity
+                id="body"
+                body-controller="eyeNeckOffset: 0 -0.11 0.09; neckHeight: 0.05"
+                networked="template: #body-template;" 
+            ></a-entity>
+            
+            <a-entity
+                id="nametag"
+                networked="template: #nametag-template; showLocalTemplate: false;"
+            ></a-entity>
+
+            <a-entity
+                id="left-hand"
+                split-axis-events
+                hand-controls2="left"
+                axis-dpad="centerZone: 1"
+                teleport-controls="cameraRig: #player-rig; teleportOrigin: #head; button: action_teleport_"
+                networked="template: #left-hand-template;"
+            >
+                <a-entity
+                    id="watch"
+                    gltf-model="assets/hud/watch.gltf"
+                    position="0 0.0015 0.147"
+                    rotation="3.5 0 0"
+                >
+                    <a-circle
+                        mute-state-indicator
+                        scale-audio-feedback="analyserSrc: #head; minScale: 0.035; maxScale: 0.08;"
+                        position="0 0.023 0"
+                        rotation="-90 0 0"
+                        scale="0.04 0.04 0.04"
+                        material="color: #d8eece; shader: flat;"
+                    ></a-circle>
                 </a-entity>
             </a-entity>
 
-            <a-entity id="right-hand" hand-controls="right" hand-controls-child-visibility axis-dpad teleport-controls="cameraRig: #player-rig; teleportOrigin: #head; hitEntity: #telepor-indicator; button: action_teleport_;"
-                      networked="template:#hand-template;showLocalTemplate:false;">
-            </a-entity>
+            <a-entity
+                id="right-hand"
+                hand-controls2="right"
+                axis-dpad
+                teleport-controls="cameraRig: #player-rig;
+                                    teleportOrigin: #head;
+                                    hitEntity: #telepor-indicator;
+                                    button: action_teleport_;"
+                networked="template: #right-hand-template;"
+            ></a-entity>
         </a-entity>
 
-        <a-entity class="head" gltf-model="#rock-island" position="0 0 0">
+        <!-- Environment -->
+        <a-entity
+            gltf-model="#rock-island"
+            position="0 0 0"
+        >
             <a-sky color="#DDFFD9"></a-sky>
         </a-entity>
     </a-scene>
+
     <script>
         document.querySelector('a-scene').addEventListener('loaded', App.onSceneLoad)
     </script>
diff --git a/src/components/avatar-customization.js b/src/components/avatar-customization.js
deleted file mode 100644
index 8346b8e821ac904d8711dffe6563643f7628ba09..0000000000000000000000000000000000000000
--- a/src/components/avatar-customization.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import Please from "pleasejs";
-
-// @TODO this whole thing is a bit of a hack. This should probably just be setting some uniforms on a sharder.
-// @TODO the palate should be indexed by alpha channel instead of the red channel.
-// @TODO color should be multiplied with the texture value to allow for texture to provide surface detail.
-// @TODO texture should be regenerated whenever headColor/LidColor values change to allow for networking, though arguably these will eventually be fetched from a users "profile" anywya, so might not be worth trying to network through NAF.
-AFRAME.registerComponent("avatar-customization", {
-  schema: {
-    headColor: { type: "color", default: null },
-    lidColor: { type: "color", default: null }
-  },
-
-  init: function() {
-    const colors = Please.make_color({
-      format: "rgb-string",
-      colors_returned: 2
-    });
-
-    this.colorMap = new Map();
-    this.colorMap.set(128, this.data.headColor || new THREE.Color(colors[0]));
-    this.colorMap.set(115, this.data.lidColor || new THREE.Color(colors[1]));
-
-    this.el.addEventListener("model-loaded", () => {
-      const map = this.el.object3D.getObjectByName("DodecAvatar_Head_0")
-        .material.map;
-      const img = map.image;
-
-      const canvas = document.createElement("canvas");
-      canvas.width = img.width;
-      canvas.height = img.height;
-      const ctx = canvas.getContext("2d");
-      ctx.drawImage(img, 0, 0, img.width, img.height);
-      const imageData = ctx.getImageData(0, 0, img.width, img.height);
-      const pixelData = imageData.data;
-
-      for (let i = 0; i < pixelData.length; i += 4) {
-        // @TODO check alpha channel and multiply colors to preserver surface detail
-        if (this.colorMap.has(pixelData[i])) {
-          const color = this.colorMap.get(pixelData[i]);
-          pixelData[i] = color.r * 255;
-          pixelData[i + 1] = color.g * 255;
-          pixelData[i + 2] = color.b * 255;
-        }
-      }
-      ctx.putImageData(imageData, 0, 0);
-      map.image = canvas;
-    });
-  }
-});
diff --git a/src/components/body-controller.js b/src/components/body-controller.js
new file mode 100644
index 0000000000000000000000000000000000000000..05afec55951aff8ba8108bd6ee95e8212d9c8b95
--- /dev/null
+++ b/src/components/body-controller.js
@@ -0,0 +1,49 @@
+AFRAME.registerComponent("body-controller", {
+  schema: {
+    camera: { type: "selector", default: "[camera]" },
+    rotationSpeed: { default: 0.07 },
+    eyeNeckOffset: { type: "vec3" },
+    neckHeight: { type: "number" }
+  },
+
+  init() {
+    this.targetAngleEuler = new THREE.Euler();
+    this.targetAngle = new THREE.Quaternion();
+    this.cameraPositionMatrix = new THREE.Matrix4();
+    const offset = this.data.eyeNeckOffset;
+    this.eyeNeckTransformMatrix = new THREE.Matrix4().makeTranslation(
+      offset.x,
+      offset.y,
+      offset.z
+    );
+    this.neckTransformMatrix = new THREE.Matrix4().makeTranslation(
+      0,
+      this.data.neckHeight,
+      0
+    );
+    this.bodyPositionMatrix = new THREE.Matrix4();
+    this.bodyPositionVector = new THREE.Vector3();
+  },
+
+  tick(time, dt) {
+    const object3D = this.el.object3D;
+    const cameraObject3D = this.data.camera.object3D;
+
+    // Set Rotation
+    this.targetAngleEuler.set(0, cameraObject3D.rotation.y, 0);
+    this.targetAngle.setFromEuler(this.targetAngleEuler);
+    object3D.quaternion.slerp(this.targetAngle, this.data.rotationSpeed);
+    const object3DRotation = object3D.rotation;
+    this.el.setAttribute("rotation", {
+      x: object3DRotation.x * THREE.Math.RAD2DEG,
+      y: object3DRotation.y * THREE.Math.RAD2DEG,
+      z: object3DRotation.z * THREE.Math.RAD2DEG
+    });
+
+    //Set Position
+    this.bodyPositionMatrix.copy(cameraObject3D.matrix);
+    this.bodyPositionMatrix.multiply(this.eyeNeckTransformMatrix);
+    this.bodyPositionVector.setFromMatrixPosition(this.bodyPositionMatrix);
+    this.el.setAttribute("position", this.bodyPositionVector);
+  }
+});
diff --git a/src/components/hand-controls-visibility.js b/src/components/hand-controls-visibility.js
deleted file mode 100644
index 50d7514897b3974e3963c3fd636bea1a9e8d3aeb..0000000000000000000000000000000000000000
--- a/src/components/hand-controls-visibility.js
+++ /dev/null
@@ -1,21 +0,0 @@
-AFRAME.registerComponent("hand-controls-visibility", {
-  init() {
-    this.onControllerConnected = this.onControllerConnected.bind(this);
-    this.onControllerDisconnected = this.onControllerDisconnected.bind(this);
-    this.el.addEventListener("controllerconnected", this.onControllerConnected);
-    this.el.addEventListener(
-      "controllerdisconnected",
-      this.onControllerDisconnected
-    );
-
-    this.el.setAttribute("visible", false);
-  },
-
-  onControllerConnected() {
-    this.el.setAttribute("visible", true);
-  },
-
-  onControllerDisconnected() {
-    this.el.setAttribute("visible", false);
-  }
-});
diff --git a/src/components/hand-controls2.js b/src/components/hand-controls2.js
new file mode 100644
index 0000000000000000000000000000000000000000..80625759a8cba93b5d8e4f0da71037735823d971
--- /dev/null
+++ b/src/components/hand-controls2.js
@@ -0,0 +1,167 @@
+const GESTURES = {
+  open: "open",
+  // point: grip active, trackpad surface active, trigger inactive.
+  point: "point",
+  // pointThumb: grip active, trigger inactive, trackpad surface inactive.
+  pointThumb: "pointThumb",
+  // fist: grip active, trigger active, trackpad surface active.
+  fist: "fist",
+  // hold: trigger active, grip inactive.
+  hold: "hold",
+  // thumbUp: grip active, trigger active, trackpad surface inactive.
+  thumbUp: "thumbUp"
+};
+
+AFRAME.registerComponent("hand-controls2", {
+  schema: { default: "left" },
+
+  init() {
+    const el = this.el;
+
+    this.gesture = GESTURES.open;
+
+    this.fingersDown = {
+      thumb: false,
+      index: false,
+      middle: false,
+      ring: false,
+      pinky: false
+    };
+
+    this.onMiddleRingPinkyDown = this.updateGesture.bind(this, {
+      middle: true,
+      ring: true,
+      pinky: true
+    });
+
+    this.onMiddleRingPinkyUp = this.updateGesture.bind(this, {
+      middle: false,
+      ring: false,
+      pinky: false
+    });
+
+    this.onIndexDown = this.updateGesture.bind(this, {
+      index: true
+    });
+
+    this.onIndexUp = this.updateGesture.bind(this, {
+      index: false
+    });
+
+    this.onThumbDown = this.updateGesture.bind(this, {
+      thumb: true
+    });
+
+    this.onThumbUp = this.updateGesture.bind(this, {
+      thumb: false
+    });
+
+    this.onControllerConnected = this.onControllerConnected.bind(this);
+    this.onControllerDisconnected = this.onControllerDisconnected.bind(this);
+
+    el.addEventListener("controllerconnected", this.onControllerConnected);
+    el.addEventListener(
+      "controllerdisconnected",
+      this.onControllerDisconnected
+    );
+
+    el.setAttribute("visible", false);
+  },
+
+  play() {
+    const el = this.el;
+    el.addEventListener("middle_ring_pinky_down", this.onMiddleRingPinkyDown);
+    el.addEventListener("middle_ring_pinky_up", this.onMiddleRingPinkyUp);
+    el.addEventListener("thumb_down", this.onThumbDown);
+    el.addEventListener("thumb_up", this.onThumbUp);
+    el.addEventListener("index_down", this.onIndexDown);
+    el.addEventListener("index_up", this.onIndexUp);
+  },
+
+  pause() {
+    const el = this.el;
+    el.removeEventListener(
+      "middle_ring_pinky_down",
+      this.onMiddleRingPinkyDown
+    );
+    el.removeEventListener("middle_ring_pinky_up", this.onMiddleRingPinkyUp);
+    el.removeEventListener("thumb_down", this.onThumbDown);
+    el.removeEventListener("thumb_up", this.onThumbUp);
+    el.removeEventListener("index_down", this.onIndexDown);
+    el.removeEventListener("index_up", this.onIndexUp);
+  },
+
+  // Attach the platform specific tracked controllers.
+  update(prevData) {
+    const el = this.el;
+    const hand = this.data;
+
+    const controlConfiguration = {
+      hand: hand,
+      model: false,
+      rotationOffset: 0
+    };
+
+    if (hand !== prevData) {
+      el.setAttribute("vive-controls", controlConfiguration);
+      el.setAttribute("oculus-touch-controls", controlConfiguration);
+      el.setAttribute("windows-motion-controls", controlConfiguration);
+    }
+  },
+
+  remove() {
+    const el = this.el;
+    el.removeEventListener("controllerconnected", this.onControllerConnected);
+    el.removeEventListener(
+      "controllerdisconnected",
+      this.onControllerDisconnected
+    );
+  },
+
+  updateGesture(nextFingersDown) {
+    Object.assign(this.fingersDown, nextFingersDown);
+    const gesture = this.determineGesture();
+
+    if (gesture !== this.gesture) {
+      this.gesture = gesture;
+      this.el.emit(this.last + "end");
+      this.el.emit(this.gesture + "start");
+    }
+  },
+
+  determineGesture() {
+    const { thumb, index, middle, ring, pinky } = this.fingersDown;
+
+    if (!thumb && !index && !middle && !ring && !pinky) {
+      return GESTURES.open;
+    } else if (thumb && index && middle && ring && pinky) {
+      return GESTURES.fist;
+    } else if (!thumb && index && middle && ring && pinky) {
+      return GESTURES.thumbUp;
+    } else if (!thumb && !index && middle && ring && pinky) {
+      return GESTURES.pointThumb;
+    } else if (!thumb && index && !middle && !ring && !pinky) {
+      return GESTURES.hold;
+    } else if (thumb && !index && !middle && !ring && !pinky) {
+      return GESTURES.hold;
+    } else if (thumb && index && !middle && !ring && !pinky) {
+      return GESTURES.hold;
+    } else if (thumb && !index && middle && ring && pinky) {
+      return GESTURES.point;
+    }
+
+    console.warn("Did not find matching gesture for ", this.fingersDown);
+
+    return GESTURES.open;
+  },
+
+  // Show controller when connected
+  onControllerConnected() {
+    this.el.setAttribute("visible", true);
+  },
+
+  // Hide controller on disconnect
+  onControllerDisconnected() {
+    this.el.setAttribute("visible", false);
+  }
+});
diff --git a/src/components/rig-selector.js b/src/components/rig-selector.js
deleted file mode 100644
index f27a7bba4654c51d6b3b6b5a6c6c9c679068d2ba..0000000000000000000000000000000000000000
--- a/src/components/rig-selector.js
+++ /dev/null
@@ -1,59 +0,0 @@
-AFRAME.registerComponent("rig-selector", {
-  schema: {
-    vive: { default: "" },
-    oculus: { default: "" },
-    daydream: { default: "" },
-    gearvr: { default: "" },
-    mobile: { default: "" },
-    desktop: { default: "" }
-  },
-  init: function() {
-    var vrDevice = this.el.sceneEl.effect.getVRDisplay();
-    var rigEl = document.createElement("a-entity");
-
-    if (vrDevice !== undefined) {
-      var displayName = vrDevice.displayName;
-
-      if (displayName.indexOf("Oculus") !== -1) {
-        rigEl.setAttribute(
-          "template",
-          "src:" + this.data.oculus || this.data.desktop
-        );
-      } else if (displayName.indexOf("OpenVR") !== -1) {
-        rigEl.setAttribute(
-          "template",
-          "src:" + this.data.vive || this.data.desktop
-        );
-      } else if (displayName.indexOf("Daydream") !== -1) {
-        rigEl.setAttribute(
-          "template",
-          "src:" + this.data.daydream || this.data.mobile
-        );
-      } else {
-        rigEl.setAttribute(
-          "template",
-          "src:" + this.data.desktop || this.data.mobile
-        );
-      }
-    } else {
-      if (AFRAME.utils.device.isGearVR()) {
-        rigEl.setAttribute(
-          "template",
-          "src:" + this.data.gearvr || this.data.mobile
-        );
-      } else if (AFRAME.utils.device.isMobile()) {
-        rigEl.setAttribute(
-          "template",
-          "src:" + this.data.mobile || this.data.desktop
-        );
-      } else {
-        rigEl.setAttribute(
-          "template",
-          "src:" + this.data.desktop || this.data.mobile
-        );
-      }
-    }
-
-    this.el.appendChild(rigEl);
-  }
-});
diff --git a/src/index.js b/src/index.js
index 9cc589523e61a8e216713f379d27dd81d0d8a9ce..ca84a32ebbb202586f0b3942afa8f70718941004 100644
--- a/src/index.js
+++ b/src/index.js
@@ -6,16 +6,19 @@ import "naf-janus-adapter";
 import "aframe-teleport-controls";
 import "aframe-input-mapping-component";
 
+import animationMixer from "aframe-extras/src/loaders/animation-mixer";
+AFRAME.registerComponent("animation-mixer", animationMixer);
+
 import "./components/axis-dpad";
 import "./components/mute-mic";
 import "./components/audio-feedback";
 import "./components/nametag-transform";
-import "./components/avatar-customization";
 import "./components/mute-state-indicator";
-import "./components/hand-controls-visibility";
+import "./components/virtual-gamepad-controls";
+import "./components/body-controller";
+import "./components/hand-controls2";
 import "./components/character-controller";
 import "./components/split-axis-events";
-import "./components/virtual-gamepad-controls";
 import "./systems/personal-space-bubble";
 
 import registerNetworkScheams from "./network-schemas";
diff --git a/src/input-mappings.js b/src/input-mappings.js
index 2f03c63032c258d2ac98da7ade61d6ea3a49d03f..b4a0dbe6dd3dfcc3eaa6238f297f8ec69c335d2b 100644
--- a/src/input-mappings.js
+++ b/src/input-mappings.js
@@ -16,7 +16,13 @@ export default function registerInputMappings() {
           menudown: "action_mute"
         },
         "oculus-touch-controls": {
-          xbuttondown: "action_mute"
+          xbuttondown: "action_mute",
+          gripdown: "middle_ring_pinky_down",
+          gripup: "middle_ring_pinky_up",
+          thumbsticktouchstart: "thumb_down",
+          thumbsticktouchend: "thumb_up",
+          triggerdown: "index_down",
+          triggerup: "index_up"
         },
         daydream: {
           menudown: "action_mute"
diff --git a/src/network-schemas.js b/src/network-schemas.js
index ac56e2be8a5f3b6028988c4a70e2610b4baa5648..987f0afe385a141f519621cb016e812b841b8bfa 100644
--- a/src/network-schemas.js
+++ b/src/network-schemas.js
@@ -11,7 +11,12 @@ function registerNetworkSchemas() {
   });
 
   NAF.schemas.add({
-    template: "#hand-template",
+    template: "#right-hand-template",
+    components: ["position", "rotation", "visible"]
+  });
+
+  NAF.schemas.add({
+    template: "#left-hand-template",
     components: ["position", "rotation", "visible"]
   });
 }
diff --git a/yarn.lock b/yarn.lock
index 113f5c6fb9ccc335115da68b845f269854c97744..ef0c717dacd6ff8e82bed56f238917614cee86fd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -52,6 +52,13 @@ acorn@^5.1.1:
   version "5.2.1"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.2.1.tgz#317ac7821826c22c702d66189ab8359675f135d7"
 
+aframe-extras@^3.12.4:
+  version "3.12.4"
+  resolved "https://registry.yarnpkg.com/aframe-extras/-/aframe-extras-3.12.4.tgz#9276bde8b51a07a9822bbce1fc55f2eb8e6810dc"
+  dependencies:
+    aframe-physics-system "^1.4.3"
+    three-pathfinding "^0.2.2"
+
 "aframe-input-mapping-component@https://github.com/fernandojsg/aframe-input-mapping-component#6ebc38f":
   version "0.1.1"
   resolved "https://github.com/fernandojsg/aframe-input-mapping-component#6ebc38f0e871e8ab66673aef5cd11f6ce052076c"
@@ -62,6 +69,13 @@ aframe-lerp-component@^1.1.0:
   dependencies:
     almost-equal "^1.1.0"
 
+aframe-physics-system@^1.4.3:
+  version "1.4.3"
+  resolved "https://registry.yarnpkg.com/aframe-physics-system/-/aframe-physics-system-1.4.3.tgz#c6927e847081bfe546658314aa4c04958ef27934"
+  dependencies:
+    cannon "github:donmccurdy/cannon.js#v0.6.2-dev1"
+    three-to-cannon "^1.1.1"
+
 "aframe-teleport-controls@https://github.com/netpro2k/aframe-teleport-controls#feature/teleport-origin":
   version "0.3.0"
   resolved "https://github.com/netpro2k/aframe-teleport-controls#41fe311d3123503ba44761acce69d0f0634139cc"
@@ -1339,6 +1353,10 @@ caniuse-lite@^1.0.30000744:
   version "1.0.30000751"
   resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000751.tgz#298ad34182ca4359757b4a93afc681b7b917e358"
 
+"cannon@github:donmccurdy/cannon.js#v0.6.2-dev1":
+  version "0.6.2"
+  resolved "https://codeload.github.com/donmccurdy/cannon.js/tar.gz/022e8ba53fa83abf0ad8a0e4fd08623123838a17"
+
 caseless@~0.12.0:
   version "0.12.0"
   resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
@@ -4171,10 +4189,6 @@ pkg-dir@^2.0.0:
   dependencies:
     find-up "^2.1.0"
 
-pleasejs@^0.4.2:
-  version "0.4.2"
-  resolved "https://registry.yarnpkg.com/pleasejs/-/pleasejs-0.4.2.tgz#aaaa1a5fa6902518de7e51e3c63b5f537f823164"
-
 pluralize@^7.0.0:
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777"
@@ -5408,6 +5422,14 @@ three-buffer-vertex-data@^1.0.0:
   dependencies:
     flatten-vertex-data "^1.0.0"
 
+three-pathfinding@^0.2.2:
+  version "0.2.3"
+  resolved "https://registry.yarnpkg.com/three-pathfinding/-/three-pathfinding-0.2.3.tgz#469bb26fb6b331f536c9ec88fde78e9c9219f637"
+
+three-to-cannon@^1.1.1:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/three-to-cannon/-/three-to-cannon-1.1.2.tgz#b0040b893b2fa5f0e8a0aedf58bc90fc07f137aa"
+
 three@^0.87.0:
   version "0.87.1"
   resolved "https://registry.yarnpkg.com/three/-/three-0.87.1.tgz#466a34edc4543459ced9b9d7d276b65216fe2ba8"