diff --git a/README.md b/README.md
index 96c5a4d06ab4f32d5a00948d0eb14d593cdd3974..78e4831b0ed256a6d47b2d3b90e4737f2a42123f 100644
--- a/README.md
+++ b/README.md
@@ -32,3 +32,5 @@ yarn build
 - `mobile` - Force mobile mode
 - `no_stats` - Disable performance stats
 - `vr_entry_type` - Either "gearvr" or "daydream". Used internally to force a VR entry type
+
+[![Waffle.io - Columns and their card count](https://badge.waffle.io/mozilla/socialmr.svg?columns=all)](http://waffle.io/mozilla/socialmr)
diff --git a/scripts/default.env b/scripts/default.env
index 82a69026a22a8eb2ad6c1da4c048095d8e3e3624..0fd18aa5be8791b61c69d2587c4611ef7f1f6b70 100644
--- a/scripts/default.env
+++ b/scripts/default.env
@@ -2,4 +2,4 @@
 # You can find more information about getting your own origin trial token here: https://github.com/GoogleChrome/OriginTrials/blob/gh-pages/developer-guide.md
 ORIGIN_TRIAL_TOKEN="AvIMoF4hyRZQVfSfksoqP+7qzwa4FSBzHRHvUyzC8rMATJVRbcOiLewBxbXtJVyV3N62gsZv7PoSNtDqqtjzYAcAAABkeyJvcmlnaW4iOiJodHRwczovL3JldGljdWx1bS5pbzo0NDMiLCJmZWF0dXJlIjoiV2ViVlIxLjFNNjIiLCJleHBpcnkiOjE1MTYxNDYyMDQsImlzU3ViZG9tYWluIjp0cnVlfQ==",
 ORIGIN_TRIAL_EXPIRES="2018-05-15",
-JANUS_SERVER="wss://dev-janus.reticulum.io"
+JANUS_SERVER="wss://prod-janus.reticulum.io"
diff --git a/src/assets/environments/cliff_meeting_space/bundle.json.tpl b/src/assets/environments/cliff_meeting_space/bundle.json.tpl
index cb321614109330cfb9d010a899c215bd740c27b2..6c54054858cec5ac982debae6351bfd6fa1664da 100644
--- a/src/assets/environments/cliff_meeting_space/bundle.json.tpl
+++ b/src/assets/environments/cliff_meeting_space/bundle.json.tpl
@@ -18,9 +18,6 @@
     {
       "name": "outdoor-geometry", "src": "<%= require("./OutdoorFacade_mesh.glb") %>"
     },
-    {
-      "name": "collision", "src": "<%= require("./FloorNav_mesh.glb") %>"
-    },
     {
       "name": "cliff-geometry", "src": "<%= require("./CliffVista_mesh.glb") %>"
     }
diff --git a/src/components/audio-feedback.js b/src/components/audio-feedback.js
index 29aafcdc45bd1fb8838b4824a9c75f1c2f471c9e..d4bdf5792e79cc1eb3191a0d042f59e89308956d 100644
--- a/src/components/audio-feedback.js
+++ b/src/components/audio-feedback.js
@@ -77,6 +77,9 @@ AFRAME.registerComponent("scale-audio-feedback", {
   },
 
   onAudioFrequencyChange(e) {
+    // TODO: come up with a cleaner way to handle this.
+    // bone's are "hidden" by scaling them with bone-visibility, without this we would overwrite that.
+    if (!this.el.object3D.visible) return;
     const { minScale, maxScale } = this.data;
     this.el.object3D.scale.setScalar(minScale + (maxScale - minScale) * e.detail.volume / 255);
   }
diff --git a/src/components/bone-visibility.js b/src/components/bone-visibility.js
new file mode 100644
index 0000000000000000000000000000000000000000..6f6f1a53e6f3eb464561ea91741d6d09e33a093f
--- /dev/null
+++ b/src/components/bone-visibility.js
@@ -0,0 +1,16 @@
+AFRAME.registerComponent("bone-visibility", {
+  tick() {
+    const { visible } = this.el.object3D;
+
+    if (this.lastVisible !== visible) {
+      if (visible) {
+        this.el.object3D.scale.set(1, 1, 1);
+      } else {
+        // Three.js doesn't like updating matrices with 0 scale, so we set it to a near zero number.
+        this.el.object3D.scale.set(0.00000001, 0.00000001, 0.00000001);
+      }
+
+      this.lastVisible = visible;
+    }
+  }
+});
diff --git a/src/components/ik-controller.js b/src/components/ik-controller.js
index 4fd89465e1d4ce97c24276b5d23d89eda5a25163..23f2dbf799f59558fd261eaa615f1bd2d37a66d2 100644
--- a/src/components/ik-controller.js
+++ b/src/components/ik-controller.js
@@ -1,5 +1,4 @@
 const { Vector3, Quaternion, Matrix4, Euler } = THREE;
-
 AFRAME.registerComponent("ik-root", {
   schema: {
     camera: { type: "string", default: ".camera" },
@@ -67,16 +66,12 @@ AFRAME.registerComponent("ik-controller", {
 
     this.hands = {
       left: {
-        lastVisible: true,
         rotation: new Matrix4().makeRotationFromEuler(new Euler(-Math.PI / 2, Math.PI / 2, 0))
       },
       right: {
-        lastVisible: true,
         rotation: new Matrix4().makeRotationFromEuler(new Euler(Math.PI / 2, Math.PI / 2, 0))
       }
     };
-
-    this.headLastVisible = true;
   },
 
   update(oldData) {
@@ -180,14 +175,6 @@ AFRAME.registerComponent("ik-controller", {
 
     this.updateHand(this.hands.left, leftHand, leftController);
     this.updateHand(this.hands.right, rightHand, rightController);
-
-    if (head.object3D.visible) {
-      if (!this.headLastVisible) {
-        head.object3D.scale.set(1, 1, 1);
-      }
-    } else if (this.headLastVisible) {
-      head.object3D.scale.set(0.0000001, 0.0000001, 0.0000001);
-    }
   },
 
   updateHand(handState, hand, controller) {
@@ -195,11 +182,13 @@ AFRAME.registerComponent("ik-controller", {
     const handMatrix = handObject3D.matrix;
     const controllerObject3D = controller.object3D;
 
+    // TODO: This coupling with personal-space-invader is not ideal.
+    // There should be some intermediate thing managing multiple opinions about object visibility
+    const spaceInvader = hand.components["personal-space-invader"];
+    const handHiddenByPersonalSpace = spaceInvader && spaceInvader.invading;
+
+    handObject3D.visible = !handHiddenByPersonalSpace && controllerObject3D.visible;
     if (controllerObject3D.visible) {
-      if (!handState.lastVisible) {
-        handObject3D.scale.set(1, 1, 1);
-        handState.lastVisible = true;
-      }
       handMatrix.multiplyMatrices(this.invRootToChest, controllerObject3D.matrix);
 
       const handControls = controller.components["hand-controls2"];
@@ -212,11 +201,6 @@ AFRAME.registerComponent("ik-controller", {
 
       handObject3D.position.setFromMatrixPosition(handMatrix);
       handObject3D.rotation.setFromRotationMatrix(handMatrix);
-    } else {
-      if (handState.lastVisible) {
-        handObject3D.scale.set(0.0000001, 0.0000001, 0.0000001);
-        handState.lastVisible = false;
-      }
     }
   }
 });
diff --git a/src/hub.html b/src/hub.html
index 014daab5a12b84ca4f65fcabe8c0f23a70454fe0..63518bec030000c91c5e26d925f7fd594b6a7e8e 100644
--- a/src/hub.html
+++ b/src/hub.html
@@ -17,8 +17,14 @@
 <body data-html-prefix="<%= HTML_PREFIX %>">
     <audio id="test-tone" src="./assets/sfx/tone.ogg"></audio>
 
-    <a-scene networked-scene="adapter: janus; audio: true; debug: true; connectOnLoad: false;" physics mute-mic="eventSrc: a-scene; toggleEvents: action_mute"
-        app-mode-input-mappings="modes: default, hud; actionSets: default, hud;">
+    <a-scene
+        networked-scene="adapter: janus; audio: true; debug: true; connectOnLoad: false;"
+        physics
+        mute-mic="eventSrc: a-scene; toggleEvents: action_mute"
+        personal-space-bubble="debug: false;"
+
+        app-mode-input-mappings="modes: default, hud; actionSets: default, hud;"
+        >
 
         <a-assets>
             <img id="unmuted" src="./assets/hud/unmuted.png">
@@ -57,7 +63,7 @@
 
                     <a-entity class="model" gltf-model-plus="inflate: true">
                         <template data-selector=".RootScene">
-                            <a-entity ik-controller animation-mixer></a-entity>
+                            <a-entity ik-controller animation-mixer space-invader-mesh="meshSelector: .Bot_Skinned"></a-entity>
                         </template>
 
                         <template data-selector=".Neck">
@@ -66,17 +72,26 @@
                             </a-entity>
                         </template>
 
+                        <template data-selector=".Chest">
+                            <a-entity personal-space-invader="radius: 0.2; useMaterial: true;" bone-visibility></a-entity>
+                        </template>
+
                         <template data-selector=".Head">
-                            <a-entity networked-audio-source networked-audio-analyser personal-space-invader>
+                            <a-entity
+                                networked-audio-source
+                                networked-audio-analyser
+                                personal-space-invader="radius: 0.15; useMaterial: true;"
+                                bone-visibility
+                            >
                             </a-entity>
                         </template>
 
                         <template data-selector=".LeftHand">
-                            <a-entity personal-space-invader></a-entity>
+                            <a-entity personal-space-invader="radius: 0.1" bone-visibility></a-entity>
                         </template>
 
                         <template data-selector=".RightHand">
-                            <a-entity personal-space-invader></a-entity>
+                            <a-entity personal-space-invader="radius: 0.1" bone-visibility></a-entity>
                         </template>
                     </a-entity>
                 </a-entity>
@@ -119,17 +134,42 @@
                 </a-entity>
             </a-entity>
 
-            <a-entity id="player-camera" class="camera" camera position="0 1.6 0" personal-space-bubble look-controls></a-entity>
-
-            <a-entity id="player-left-controller" class="left-controller" hand-controls2="left" tracked-controls teleport-controls="cameraRig: #player-rig; teleportOrigin: #player-camera; button: action_teleport_"
-                app-mode-toggle-playing__teleport-controls="mode: hud; invert: true;" haptic-feedback></a-entity>
-
-
-
-            <a-entity id="player-right-controller" class="right-controller" hand-controls2="right" tracked-controls teleport-controls="cameraRig: #player-rig; teleportOrigin: #player-camera; button: action_teleport_"
-                haptic-feedback raycaster="objects:.hud; showLine: true; far: 2;" cursor="fuse: false; downEvents: action_ui_select_down; upEvents: action_ui_select_up;"
-                app-mode-toggle-playing__teleport-controls="mode: hud; invert: true;" app-mode-toggle-playing__raycaster="mode: hud;"
-                app-mode-toggle-playing__cursor="mode: hud;" app-mode-toggle-attribute__line="mode: hud; property: visible;"></a-entity>
+            <a-entity
+                id="player-camera"
+                class="camera"
+                camera
+                position="0 1.6 0"
+                personal-space-bubble="radius: 0.4"
+                look-controls
+            ></a-entity>
+
+            <a-entity
+                id="player-left-controller"
+                class="left-controller"
+                hand-controls2="left"
+                tracked-controls
+                teleport-controls="cameraRig: #player-rig; teleportOrigin: #player-camera; button: action_teleport_"
+                app-mode-toggle-playing__teleport-controls="mode: hud; invert: true;"
+                haptic-feedback
+            ></a-entity>
+
+
+
+            <a-entity
+                id="player-right-controller"
+                class="right-controller"
+                hand-controls2="right"
+                tracked-controls
+                teleport-controls="cameraRig: #player-rig; teleportOrigin: #player-camera; button: action_teleport_"
+                haptic-feedback
+                raycaster="objects:.hud; showLine: true; far: 2;"
+                cursor="fuse: false; downEvents: action_ui_select_down; upEvents: action_ui_select_up;"
+
+                app-mode-toggle-playing__teleport-controls="mode: hud; invert: true;"
+                app-mode-toggle-playing__raycaster="mode: hud;"
+                app-mode-toggle-playing__cursor="mode: hud;"
+                app-mode-toggle-attribute__line="mode: hud; property: visible;"
+            ></a-entity>
 
             <a-entity gltf-model-plus="inflate: true;" class="model">
                 <template data-selector=".RootScene">
@@ -143,22 +183,36 @@
                 </template>
 
                 <template data-selector=".Head">
-                    <a-entity visible="false"></a-entity>
+                    <a-entity visible="false" bone-visibility></a-entity>
                 </template>
 
                 <template data-selector=".LeftHand">
-                    <a-entity>
-                        <a-entity id="watch" gltf-model-plus="src: #watch-model" bone-mute-state-indicator scale="1.5 1.5 1.5" rotation="0 -90 90"
-                            position="0 -0.04 0"></a--entity>
-                            <a-entity event-repeater="events: action_grab, action_release; eventSource: #player-left-controller" static-body="shape: sphere; sphereRadius: 0.02"
-                                mixin="super-hands" position="0 0.05 0"></a-entity>
-                        </a-entity>
+                    <a-entity bone-visibility>
+                        <a-entity
+                            id="watch"
+                            gltf-model-plus="src: #watch-model"
+                            bone-mute-state-indicator
+                            scale="1.5 1.5 1.5"
+                            rotation="0 -90 90"
+                            position="0 -0.04 0"
+                        ></a--entity>
+                        <a-entity
+                            event-repeater="events: action_grab, action_release; eventSource: #player-left-controller"
+                            static-body="shape: sphere; sphereRadius: 0.02"
+                            mixin="super-hands"
+                            position="0 0.05 0"
+                        ></a-entity>
+                    </a-entity>
                 </template>
 
                 <template data-selector=".RightHand">
-                    <a-entity>
-                        <a-entity event-repeater="events: action_grab, action_release; eventSource: #player-right-controller" static-body="shape: sphere; sphereRadius: 0.02"
-                            mixin="super-hands" position="0 -0.05 0"></a-entity>
+                    <a-entity bone-visibility>
+                        <a-entity
+                            event-repeater="events: action_grab, action_release; eventSource: #player-right-controller"
+                            static-body="shape: sphere; sphereRadius: 0.02"
+                            mixin="super-hands"
+                            position="0 -0.05 0"
+                        ></a-entity>
                     </a-entity>
                 </template>
 
diff --git a/src/hub.js b/src/hub.js
index ed45825ea1de7378b6d6a44152d287ff8f7ba0c4..9df7c45a4476b0f69f51264864415308f076c8ad 100644
--- a/src/hub.js
+++ b/src/hub.js
@@ -23,6 +23,7 @@ import "./components/wasd-to-analog2d"; //Might be a behaviour or activator in t
 import "./components/mute-mic";
 import "./components/audio-feedback";
 import "./components/bone-mute-state-indicator";
+import "./components/bone-visibility";
 import "./components/in-world-hud";
 import "./components/virtual-gamepad-controls";
 import "./components/ik-controller";
diff --git a/src/input-mappings.js b/src/input-mappings.js
index a5338250079c08c94d79edb84457ff4e3b4e7b65..035110dd677efa748c280ae0d6b8f14fde1ae6f4 100644
--- a/src/input-mappings.js
+++ b/src/input-mappings.js
@@ -31,14 +31,19 @@ const config = {
   mappings: {
     default: {
       "vive-controls": {
-        menudown: "action_mute",
+        menudown: ["action_mute", "thumb_down"],
+        menuup: "thumb_up",
         "trackpad.pressedmove": { left: "move" },
         trackpad_dpad4_pressed_west_down: { right: "snap_rotate_left" },
         trackpad_dpad4_pressed_east_down: { right: "snap_rotate_right" },
         trackpad_dpad4_pressed_center_down: { right: "action_teleport_down" },
         trackpadup: { right: "action_teleport_up" },
-        gripdown: "action_grab",
-        gripup: "action_release"
+        gripdown: ["action_grab", "middle_ring_pinky_down", "index_down"],
+        gripup: ["action_release", "middle_ring_pinky_up", "index_up"],
+        trackpadtouchstart: "thumb_down",
+        trackpadtouchend: "thumb_up",
+        triggerdown: "index_down",
+        triggerup: "index_up"
       },
       "oculus-touch-controls": {
         joystick_dpad4_west: {
diff --git a/src/systems/personal-space-bubble.js b/src/systems/personal-space-bubble.js
index e696705e138159a67c79e9707fe97bada64a9a58..bfd648a96fb7f98129b2cd1231f3466932ab4f93 100644
--- a/src/systems/personal-space-bubble.js
+++ b/src/systems/personal-space-bubble.js
@@ -2,93 +2,188 @@ const invaderPos = new AFRAME.THREE.Vector3();
 const bubblePos = new AFRAME.THREE.Vector3();
 
 AFRAME.registerSystem("personal-space-bubble", {
+  schema: {
+    debug: { default: false }
+  },
+
   init() {
     this.invaders = [];
     this.bubbles = [];
   },
 
-  registerBubble(el) {
-    this.bubbles.push(el);
+  registerBubble(bubble) {
+    this.bubbles.push(bubble);
   },
 
-  unregisterBubble(el) {
-    const index = this.bubbles.indexOf(el);
+  unregisterBubble(bubble) {
+    const index = this.bubbles.indexOf(bubble);
 
     if (index !== -1) {
       this.bubbles.splice(index, 1);
     }
   },
 
-  registerInvader(el) {
-    NAF.utils.getNetworkedEntity(el).then(networkedEl => {
+  registerInvader(invader) {
+    NAF.utils.getNetworkedEntity(invader.el).then(networkedEl => {
       const owner = NAF.utils.getNetworkOwner(networkedEl);
 
       if (owner !== NAF.clientId) {
-        this.invaders.push(el);
+        this.invaders.push(invader);
       }
     });
   },
 
-  unregisterInvader(el) {
-    const index = this.invaders.indexOf(el);
+  unregisterInvader(invader) {
+    const index = this.invaders.indexOf(invader);
 
     if (index !== -1) {
       this.invaders.splice(index, 1);
     }
   },
 
+  update() {
+    for (let i = 0; i < this.bubbles.length; i++) {
+      this.bubbles[i].updateDebug();
+    }
+
+    for (let i = 0; i < this.invaders.length; i++) {
+      this.invaders[i].updateDebug();
+    }
+  },
+
   tick() {
     // Update matrix positions once for each space bubble and space invader
     for (let i = 0; i < this.bubbles.length; i++) {
-      this.bubbles[i].object3D.updateMatrixWorld(true);
+      this.bubbles[i].el.object3D.updateMatrixWorld(true);
     }
 
     for (let i = 0; i < this.invaders.length; i++) {
-      this.invaders[i].object3D.updateMatrixWorld(true);
+      this.invaders[i].el.object3D.updateMatrixWorld(true);
+      this.invaders[i].setInvading(false);
     }
 
     // Loop through all of the space bubbles (usually one)
     for (let i = 0; i < this.bubbles.length; i++) {
       const bubble = this.bubbles[i];
 
-      bubblePos.setFromMatrixPosition(bubble.object3D.matrixWorld);
-
-      const radius = bubble.components["personal-space-bubble"].data.radius;
-      const radiusSquared = radius * radius;
+      bubblePos.setFromMatrixPosition(bubble.el.object3D.matrixWorld);
 
       // Hide the invader if inside the bubble
       for (let j = 0; j < this.invaders.length; j++) {
         const invader = this.invaders[j];
 
-        invaderPos.setFromMatrixPosition(invader.object3D.matrixWorld);
-
-        const distanceSquared = bubblePos.distanceTo(invaderPos);
+        invaderPos.setFromMatrixPosition(invader.el.object3D.matrixWorld);
 
-        invader.object3D.visible = distanceSquared > radiusSquared;
+        const distanceSquared = bubblePos.distanceToSquared(invaderPos);
+        const radiusSum = bubble.data.radius + invader.data.radius;
+        if (distanceSquared < radiusSum * radiusSum) {
+          invader.setInvading(true);
+        }
       }
     }
   }
 });
 
+function createSphereGizmo(radius) {
+  const geometry = new THREE.SphereBufferGeometry(radius, 10, 10);
+  const wireframe = new THREE.WireframeGeometry(geometry);
+  const line = new THREE.LineSegments(wireframe);
+  line.material.opacity = 0.5;
+  line.material.transparent = true;
+  return line;
+}
+
+// TODO: we need to come up with a more generic way of doing this as this is very specific to our avatars.
+AFRAME.registerComponent("space-invader-mesh", {
+  schema: {
+    meshSelector: { type: "string" }
+  },
+  init() {
+    this.targetMesh = this.el.querySelector(this.data.meshSelector).object3DMap.skinnedmesh;
+  }
+});
+
+function findInvaderMesh(entity) {
+  while (entity && !(entity.components && entity.components["space-invader-mesh"])) {
+    entity = entity.parentNode;
+  }
+  return entity && entity.components["space-invader-mesh"].targetMesh;
+}
+
+const DEBUG_OBJ = "psb-debug";
+
 AFRAME.registerComponent("personal-space-invader", {
+  schema: {
+    radius: { type: "number", default: 0.1 },
+    useMaterial: { default: false },
+    debug: { default: false },
+    invadingOpacity: { default: 0.3 }
+  },
+
   init() {
-    this.el.sceneEl.systems["personal-space-bubble"].registerInvader(this.el);
+    const system = this.el.sceneEl.systems["personal-space-bubble"];
+    system.registerInvader(this);
+    if (this.data.useMaterial) {
+      const mesh = findInvaderMesh(this.el);
+      if (mesh) {
+        this.targetMaterial = mesh.material;
+      }
+    }
+    this.invading = false;
+  },
+
+  update() {
+    this.radiusSquared = this.data.radius * this.data.radius;
+    this.updateDebug();
+  },
+
+  updateDebug() {
+    const system = this.el.sceneEl.systems["personal-space-bubble"];
+    if (system.data.debug || this.data.debug) {
+      !this.el.object3DMap[DEBUG_OBJ] && this.el.setObject3D(DEBUG_OBJ, createSphereGizmo(this.data.radius));
+    } else if (this.el.object3DMap[DEBUG_OBJ]) {
+      this.el.removeObject3D(DEBUG_OBJ);
+    }
   },
 
   remove() {
-    this.el.sceneEl.systems["personal-space-bubble"].unregisterInvader(this.el);
+    this.el.sceneEl.systems["personal-space-bubble"].unregisterInvader(this);
+  },
+
+  setInvading(invading) {
+    if (this.targetMaterial) {
+      this.targetMaterial.opacity = invading ? this.data.invadingOpacity : 1;
+      this.targetMaterial.transparent = invading;
+    } else {
+      this.el.object3D.visible = !invading;
+    }
+    this.invading = invading;
   }
 });
 
 AFRAME.registerComponent("personal-space-bubble", {
   schema: {
-    radius: { type: "number", default: 0.8 }
+    radius: { type: "number", default: 0.8 },
+    debug: { default: false }
   },
   init() {
-    this.system.registerBubble(this.el);
+    this.system.registerBubble(this);
+  },
+
+  update() {
+    this.radiusSquared = this.data.radius * this.data.radius;
+    this.updateDebug();
+  },
+
+  updateDebug() {
+    if (this.system.data.debug || this.data.debug) {
+      !this.el.object3DMap[DEBUG_OBJ] && this.el.setObject3D(DEBUG_OBJ, createSphereGizmo(this.data.radius));
+    } else if (this.el.object3DMap[DEBUG_OBJ]) {
+      this.el.removeObject3D(DEBUG_OBJ);
+    }
   },
 
   remove() {
-    this.system.unregisterBubble(this.el);
+    this.system.unregisterBubble(this);
   }
 });