diff --git a/src/components/gltf-model-plus.js b/src/components/gltf-model-plus.js
index 686245b492555beaf311a7f2f9b94c07dd600d2a..61445a2bd6185ac8ca621a2e1a1351ccb207302f 100644
--- a/src/components/gltf-model-plus.js
+++ b/src/components/gltf-model-plus.js
@@ -74,16 +74,34 @@ function cloneGltf(gltf) {
   return clone;
 }
 
-const inflateEntities = function(parentEl, node, gltfPath) {
-  // setObject3D mutates the node's parent, so we have to copy
-  const children = node.children.slice(0);
+/// Walks the tree of three.js objects starting at the given node, using the GLTF data
+/// and template data to construct A-Frame entities and components when necessary.
+/// (It's unnecessary to construct entities for subtrees that have no component data
+/// or templates associated with any of their nodes.)
+///
+/// Returns the A-Frame entity associated with the given node, if one was constructed.
+const inflateEntities = function(node, templates, gltfPath) {
+  // inflate subtrees first so that we can determine whether or not this node needs to be inflated
+  const childEntities = [];
+  const children = node.children.slice(0); // setObject3D mutates the node's parent, so we have to copy
+  for (const child of children) {
+    const el = inflateEntities(child, templates, gltfPath);
+    if (el) {
+      childEntities.push(el);
+    }
+  }
+
+  const nodeHasBehavior = node.userData.components || node.name in templates;
+  if (!nodeHasBehavior && !childEntities.length) {
+    return null; // we don't need an entity for this node
+  }
 
   const el = document.createElement("a-entity");
+  el.append.apply(el, childEntities);
 
   // Remove invalid CSS class name characters.
   const className = (node.name || node.uuid).replace(/[^\w-]/g, "");
   el.classList.add(className);
-  parentEl.appendChild(el);
 
   // AFRAME rotation component expects rotations in YXZ, convert it
   if (node.rotation.order !== "YXZ") {
@@ -138,15 +156,11 @@ const inflateEntities = function(parentEl, node, gltfPath) {
     }
   }
 
-  children.forEach(childNode => {
-    inflateEntities(el, childNode, gltfPath);
-  });
-
   return el;
 };
 
-function attachTemplate(root, { selector, templateRoot }) {
-  const targetEls = root.querySelectorAll(selector);
+function attachTemplate(root, name, templateRoot) {
+  const targetEls = root.querySelectorAll("." + name);
   for (const el of targetEls) {
     const root = templateRoot.cloneNode(true);
     // Merge root element attributes with the target element
@@ -216,13 +230,11 @@ AFRAME.registerComponent("gltf-model-plus", {
   },
 
   loadTemplates() {
-    this.templates = [];
-    this.el.querySelectorAll(":scope > template").forEach(templateEl =>
-      this.templates.push({
-        selector: templateEl.getAttribute("data-selector"),
-        templateRoot: document.importNode(templateEl.firstElementChild || templateEl.content.firstElementChild, true)
-      })
-    );
+    this.templates = {};
+    this.el.querySelectorAll(":scope > template").forEach(templateEl => {
+      const root = document.importNode(templateEl.firstElementChild || templateEl.content.firstElementChild, true);
+      this.templates[templateEl.getAttribute("data-name")] = root;
+    });
   },
 
   async applySrc(src) {
@@ -260,17 +272,20 @@ AFRAME.registerComponent("gltf-model-plus", {
       this.el.setObject3D("mesh", this.model);
 
       if (this.data.inflate) {
-        this.inflatedEl = inflateEntities(this.el, this.model, gltfPath);
+        this.inflatedEl = inflateEntities(this.model, this.templates, gltfPath);
+        this.el.appendChild(this.inflatedEl);
         // TODO: Still don't fully understand the lifecycle here and how it differs between browsers, we should dig in more
         // Wait one tick for the appended custom elements to be connected before attaching templates
         await nextTick();
         if (src != this.lastSrc) return; // TODO: there must be a nicer pattern for this
-        this.templates.forEach(attachTemplate.bind(null, this.el));
+        for (const name in this.templates) {
+          attachTemplate(this.el, name, this.templates[name]);
+        }
       }
 
       this.el.emit("model-loaded", { format: "gltf", model: this.model });
     } catch (e) {
-      console.error("Failed to load glTF model", e.message, this);
+      console.error("Failed to load glTF model", e, this);
       this.el.emit("model-error", { format: "gltf", src });
     }
   },
diff --git a/src/components/ik-controller.js b/src/components/ik-controller.js
index 3b2f3026a56ce64afb9460c6c6de0f37ccc6d969..1bf9e71de656aa9b4972cdf59e534af78599493b 100644
--- a/src/components/ik-controller.js
+++ b/src/components/ik-controller.js
@@ -39,14 +39,14 @@ function findIKRoot(entity) {
  */
 AFRAME.registerComponent("ik-controller", {
   schema: {
-    leftEye: { type: "string", default: ".LeftEye" },
-    rightEye: { type: "string", default: ".RightEye" },
-    head: { type: "string", default: ".Head" },
-    neck: { type: "string", default: ".Neck" },
-    leftHand: { type: "string", default: ".LeftHand" },
-    rightHand: { type: "string", default: ".RightHand" },
-    chest: { type: "string", default: ".Chest" },
-    hips: { type: "string", default: ".Hips" },
+    leftEye: { type: "string", default: "LeftEye" },
+    rightEye: { type: "string", default: "RightEye" },
+    head: { type: "string", default: "Head" },
+    neck: { type: "string", default: "Neck" },
+    leftHand: { type: "string", default: "LeftHand" },
+    rightHand: { type: "string", default: "RightHand" },
+    chest: { type: "string", default: "Chest" },
+    hips: { type: "string", default: "Hips" },
     rotationSpeed: { default: 5 }
   },
 
@@ -86,46 +86,46 @@ AFRAME.registerComponent("ik-controller", {
 
   update(oldData) {
     if (this.data.leftEye !== oldData.leftEye) {
-      this.leftEye = this.el.querySelector(this.data.leftEye);
+      this.leftEye = this.el.object3D.getObjectByName(this.data.leftEye);
     }
 
     if (this.data.rightEye !== oldData.rightEye) {
-      this.rightEye = this.el.querySelector(this.data.rightEye);
+      this.rightEye = this.el.object3D.getObjectByName(this.data.rightEye);
     }
 
     if (this.data.head !== oldData.head) {
-      this.head = this.el.querySelector(this.data.head);
+      this.head = this.el.object3D.getObjectByName(this.data.head);
     }
 
     if (this.data.neck !== oldData.neck) {
-      this.neck = this.el.querySelector(this.data.neck);
+      this.neck = this.el.object3D.getObjectByName(this.data.neck);
     }
 
     if (this.data.leftHand !== oldData.leftHand) {
-      this.leftHand = this.el.querySelector(this.data.leftHand);
+      this.leftHand = this.el.object3D.getObjectByName(this.data.leftHand);
     }
 
     if (this.data.rightHand !== oldData.rightHand) {
-      this.rightHand = this.el.querySelector(this.data.rightHand);
+      this.rightHand = this.el.object3D.getObjectByName(this.data.rightHand);
     }
 
     if (this.data.chest !== oldData.chest) {
-      this.chest = this.el.querySelector(this.data.chest);
+      this.chest = this.el.object3D.getObjectByName(this.data.chest);
     }
 
     if (this.data.hips !== oldData.hips) {
-      this.hips = this.el.querySelector(this.data.hips);
+      this.hips = this.el.object3D.getObjectByName(this.data.hips);
     }
 
     // Set middleEye's position to be right in the middle of the left and right eyes.
-    this.middleEyePosition.addVectors(this.leftEye.object3D.position, this.rightEye.object3D.position);
+    this.middleEyePosition.addVectors(this.leftEye.position, this.rightEye.position);
     this.middleEyePosition.divideScalar(2);
     this.middleEyeMatrix.makeTranslation(this.middleEyePosition.x, this.middleEyePosition.y, this.middleEyePosition.z);
     this.invMiddleEyeToHead = this.middleEyeMatrix.getInverse(this.middleEyeMatrix);
 
     this.invHipsToHeadVector
-      .addVectors(this.chest.object3D.position, this.neck.object3D.position)
-      .add(this.head.object3D.position)
+      .addVectors(this.chest.position, this.neck.position)
+      .add(this.head.position)
       .negate();
   },
 
@@ -162,35 +162,30 @@ AFRAME.registerComponent("ik-controller", {
 
     // Then position the hips such that the head is aligned with headTransform
     // (which positions middleEye in line with the hmd)
-    hips.object3D.position.setFromMatrixPosition(headTransform).add(invHipsToHeadVector);
+    hips.position.setFromMatrixPosition(headTransform).add(invHipsToHeadVector);
 
     // Animate the hip rotation to follow the Y rotation of the camera with some damping.
     cameraYRotation.setFromRotationMatrix(cameraForward, "YXZ");
     cameraYRotation.x = 0;
     cameraYRotation.z = 0;
     cameraYQuaternion.setFromEuler(cameraYRotation);
-    Quaternion.slerp(
-      hips.object3D.quaternion,
-      cameraYQuaternion,
-      hips.object3D.quaternion,
-      this.data.rotationSpeed * dt / 1000
-    );
+    Quaternion.slerp(hips.quaternion, cameraYQuaternion, hips.quaternion, this.data.rotationSpeed * dt / 1000);
 
     // Take the head orientation computed from the hmd, remove the Y rotation already applied to it by the hips,
     // and apply it to the head
-    invHipsQuaternion.copy(hips.object3D.quaternion).inverse();
-    head.object3D.quaternion.setFromRotationMatrix(headTransform).premultiply(invHipsQuaternion);
+    invHipsQuaternion.copy(hips.quaternion).inverse();
+    head.quaternion.setFromRotationMatrix(headTransform).premultiply(invHipsQuaternion);
 
-    hips.object3D.updateMatrix();
-    rootToChest.multiplyMatrices(hips.object3D.matrix, chest.object3D.matrix);
+    hips.updateMatrix();
+    rootToChest.multiplyMatrices(hips.matrix, chest.matrix);
     invRootToChest.getInverse(rootToChest);
 
     this.updateHand(this.hands.left, leftHand, leftController);
     this.updateHand(this.hands.right, rightHand, rightController);
   },
 
-  updateHand(handState, hand, controller) {
-    const handObject3D = hand.object3D;
+  updateHand(handState, handObject3D, controller) {
+    const hand = handObject3D.el;
     const handMatrix = handObject3D.matrix;
     const controllerObject3D = controller.object3D;
 
diff --git a/src/hub.html b/src/hub.html
index f71396377f6f10cb02d076f08b430242c01522bf..3438ab9a046581e9e5f940e25a98ede7ff88bb91 100644
--- a/src/hub.html
+++ b/src/hub.html
@@ -100,11 +100,11 @@
                     <a-entity class="right-controller"></a-entity>
 
                     <a-entity class="model" gltf-model-plus="inflate: true">
-                        <template data-selector=".RootScene">
+                        <template data-name="RootScene">
                             <a-entity ik-controller hand-pose__left hand-pose__right animation-mixer space-invader-mesh="meshSelector: .Bot_Skinned"></a-entity>
                         </template>
 
-                        <template data-selector=".Neck">
+                        <template data-name="Neck">
                             <a-entity>
                                 <a-entity
                                    class="nametag"
@@ -116,7 +116,7 @@
                             </a-entity>
                         </template>
 
-                        <template data-selector=".Chest">
+                        <template data-name="Chest">
                             <a-entity>
                               <a-entity personal-space-invader="radius: 0.2; useMaterial: true;" bone-visibility> </a-entity>
                               <a-entity billboard>
@@ -126,29 +126,34 @@
                             </a-entity>
                         </template>
 
-                        <template data-selector=".Head">
+                        <template data-name="Head">
                             <a-entity
                                 networked-audio-source
                                 networked-audio-analyser
                                 personal-space-invader="radius: 0.15; useMaterial: true;"
                                 bone-visibility
                             >
-                            <a-cylinder
-                                static-body
-                                radius="0.13"
-                                height="0.2"
-                                position="0 0.07 0.05"
-                                visible="false"
-                            ></a-cylinder>
+                              <a-cylinder
+                                  static-body
+                                  radius="0.13"
+                                  height="0.2"
+                                  position="0 0.07 0.05"
+                                  visible="false"
+                              ></a-cylinder>
                             </a-entity>
                         </template>
 
-                        <template data-selector=".LeftHand">
-                            <a-entity personal-space-invader="radius: 0.1" bone-visibility></a-entity>
+                        <!-- needs to exist for the benefit of the personal space calculator  -->
+                        <template data-name="Bot_Skinned">
+                          <a-entity></a-entity>
                         </template>
 
-                        <template data-selector=".RightHand">
-                            <a-entity personal-space-invader="radius: 0.1" bone-visibility></a-entity>
+                        <template data-name="LeftHand">
+                          <a-entity personal-space-invader="radius: 0.1" bone-visibility></a-entity>
+                        </template>
+
+                        <template data-name="RightHand">
+                          <a-entity personal-space-invader="radius: 0.1" bone-visibility></a-entity>
                         </template>
                     </a-entity>
                 </a-entity>
@@ -230,14 +235,14 @@
             ></a-mixin>
 
             <a-mixin id="controller-super-hands"
-                super-hands="
-                    colliderEvent: collisions; colliderEventProperty: els;
-                    colliderEndEvent: collisions; colliderEndEventProperty: clearedEls;
-                    grabStartButtons: hand_grab; grabEndButtons: hand_release;
-                    stretchStartButtons: hand_grab; stretchEndButtons: hand_release;
-                    dragDropStartButtons: hand_grab; dragDropEndButtons: hand_release;"
-                collision-filter="collisionForces: false"
-                physics-collider
+                     super-hands="
+                         colliderEvent: collisions; colliderEventProperty: els;
+                         colliderEndEvent: collisions; colliderEndEventProperty: clearedEls;
+                         grabStartButtons: hand_grab; grabEndButtons: hand_release;
+                         stretchStartButtons: hand_grab; stretchEndButtons: hand_release;
+                         dragDropStartButtons: hand_grab; dragDropEndButtons: hand_release;"
+                     collision-filter="collisionForces: false"
+                     physics-collider
             ></a-mixin>
         </a-assets>
 
@@ -283,121 +288,121 @@
             networked-avatar
             cardboard-controls
         >
-            <a-entity
-                id="player-hud"
-                hud-controller="head: #player-camera;"
-                vr-mode-toggle-visibility
-                vr-mode-toggle-playing__hud-controller
-            >
-                <a-entity in-world-hud="haptic:#player-right-controller;raycaster:#player-right-controller;" rotation="30 0 0">
-                    <a-rounded height="0.13" width="0.48" color="#000000" position="-0.24 -0.065 0" radius="0.065" opacity="0.35" class="hud bg"></a-rounded>
-                    <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Mute Mic; activeTooltipText: Unmute Mic; image: #mute-off; hoverImage: #mute-off-hover; activeImage: #mute-on; activeHoverImage: #mute-on-hover" scale="0.1 0.1 0.1" position="-0.17 0 0.001" class="ui hud mic" material="alphaTest:0.1;"></a-image>
-                    <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Pause; activeTooltipText: Resume; image: #freeze-off; hoverImage: #freeze-off-hover; activeImage: #freeze-on; activeHoverImage: #freeze-on-hover" scale="0.2 0.2 0.2" position="0 0 0.005" class="ui hud freeze"></a-image>
-                    <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Enable Bubble; activeTooltipText: Disable Bubble; image: #bubble-off; hoverImage: #bubble-off-hover; activeImage: #bubble-on; activeHoverImage: #bubble-on-hover" scale="0.1 0.1 0.1" position="0.17 0 0.001" class="ui hud bubble" material="alphaTest:0.1;"></a-image>
-                    <a-rounded visible="false" id="hud-tooltip" height="0.08" width="0.3" color="#000000" position="-0.15 -0.2 0" rotation="-20 0 0" radius="0.025" opacity="0.35" class="hud bg">
-                        <a-entity text="value: Mute Mic; align:center;" position="0.15 0.04 0.001" ></a-entity>
-                    </a-rounded>
-                </a-entity>
+          <a-entity
+              id="player-hud"
+              hud-controller="head: #player-camera;"
+              vr-mode-toggle-visibility
+              vr-mode-toggle-playing__hud-controller
+          >
+            <a-entity in-world-hud="haptic:#player-right-controller;raycaster:#player-right-controller;" rotation="30 0 0">
+              <a-rounded height="0.13" width="0.48" color="#000000" position="-0.24 -0.065 0" radius="0.065" opacity="0.35" class="hud bg"></a-rounded>
+              <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Mute Mic; activeTooltipText: Unmute Mic; image: #mute-off; hoverImage: #mute-off-hover; activeImage: #mute-on; activeHoverImage: #mute-on-hover" scale="0.1 0.1 0.1" position="-0.17 0 0.001" class="ui hud mic" material="alphaTest:0.1;"></a-image>
+              <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Pause; activeTooltipText: Resume; image: #freeze-off; hoverImage: #freeze-off-hover; activeImage: #freeze-on; activeHoverImage: #freeze-on-hover" scale="0.2 0.2 0.2" position="0 0 0.005" class="ui hud freeze"></a-image>
+              <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Enable Bubble; activeTooltipText: Disable Bubble; image: #bubble-off; hoverImage: #bubble-off-hover; activeImage: #bubble-on; activeHoverImage: #bubble-on-hover" scale="0.1 0.1 0.1" position="0.17 0 0.001" class="ui hud bubble" material="alphaTest:0.1;"></a-image>
+              <a-rounded visible="false" id="hud-tooltip" height="0.08" width="0.3" color="#000000" position="-0.15 -0.2 0" rotation="-20 0 0" radius="0.025" opacity="0.35" class="hud bg">
+                <a-entity text="value: Mute Mic; align:center;" position="0.15 0.04 0.001" ></a-entity>
+              </a-rounded>
             </a-entity>
-
+          </a-entity>
+
+          <a-entity
+              id="player-camera"
+              class="camera"
+              camera
+              position="0 1.6 0"
+              personal-space-bubble="radius: 0.4"
+              pitch-yaw-rotator
+          >
             <a-entity
-                id="player-camera"
-                class="camera"
-                camera
-                position="0 1.6 0"
-                personal-space-bubble="radius: 0.4"
-                pitch-yaw-rotator
-            >
-                <a-entity
-                    id="gaze-teleport"
-                    position = "0.15 0 0"
-                    teleport-controls="
-                    cameraRig: #player-rig;
-                    teleportOrigin: #player-camera;
-                    button: gaze-teleport_;
-                    collisionEntities: [nav-mesh];
-                    drawIncrementally: true;
-                    incrementalDrawMs: 600;
-                    hitOpacity: 0.3;
-                    missOpacity: 0.2;"
-                ></a-entity>
-                <a-entity id="player-camera-reverse-z" rotation="0 180 0"></a-entity>
-            </a-entity>
-
-            <a-entity
-                id="player-left-controller"
-                class="left-controller"
-                hand-controls2="left"
-                tracked-controls
+                id="gaze-teleport"
+                position = "0.15 0 0"
                 teleport-controls="
                     cameraRig: #player-rig;
                     teleportOrigin: #player-camera;
-                    button: cursor-teleport_;
-                    collisionEntities: [nav-mesh];
-                    drawIncrementally: true;
-                    incrementalDrawMs: 600;
-                    hitOpacity: 0.3;
-                    missOpacity: 0.2;"
-                haptic-feedback
-                body="type: static; shape: none;"
-                mixin="controller-super-hands"
-                controls-shape-offset
-            >
-                <a-entity id="player-left-controller-reverse-z" rotation="0 180 0"></a-entity>
-            </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: cursor-teleport_;
+                    button: gaze-teleport_;
                     collisionEntities: [nav-mesh];
                     drawIncrementally: true;
                     incrementalDrawMs: 600;
                     hitOpacity: 0.3;
                     missOpacity: 0.2;"
-                haptic-feedback
-                body="type: static; shape: none;"
-                mixin="controller-super-hands"
-                controls-shape-offset
-            >
-                <a-entity id="player-right-controller-reverse-z" rotation="0 180 0"></a-entity>
-            </a-entity>
+            ></a-entity>
+            <a-entity id="player-camera-reverse-z" rotation="0 180 0"></a-entity>
+          </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: cursor-teleport_;
+                  collisionEntities: [nav-mesh];
+                  drawIncrementally: true;
+                  incrementalDrawMs: 600;
+                  hitOpacity: 0.3;
+                  missOpacity: 0.2;"
+              haptic-feedback
+              body="type: static; shape: none;"
+              mixin="controller-super-hands"
+              controls-shape-offset
+          >
+            <a-entity id="player-left-controller-reverse-z" rotation="0 180 0"></a-entity>
+          </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: cursor-teleport_;
+                  collisionEntities: [nav-mesh];
+                  drawIncrementally: true;
+                  incrementalDrawMs: 600;
+                  hitOpacity: 0.3;
+                  missOpacity: 0.2;"
+              haptic-feedback
+              body="type: static; shape: none;"
+              mixin="controller-super-hands"
+              controls-shape-offset
+          >
+            <a-entity id="player-right-controller-reverse-z" rotation="0 180 0"></a-entity>
+          </a-entity>
+
+          <a-entity gltf-model-plus="inflate: true;"
+                    class="model">
+            <template data-name="RootScene">
+              <a-entity
+                  ik-controller
+                  animation-mixer
+                  hand-pose__left
+                  hand-pose__right
+                  hand-pose-controller__left="networkedAvatar:#player-rig;eventSrc:#player-left-controller"
+                  hand-pose-controller__right="networkedAvatar:#player-rig;eventSrc:#player-right-controller"
+              ></a-entity>
+            </template>
 
-            <a-entity gltf-model-plus="inflate: true;"
-                      class="model">
-                <template data-selector=".RootScene">
-                    <a-entity
-                        ik-controller
-                        animation-mixer
-                        hand-pose__left
-                        hand-pose__right
-                        hand-pose-controller__left="networkedAvatar:#player-rig;eventSrc:#player-left-controller"
-                        hand-pose-controller__right="networkedAvatar:#player-rig;eventSrc:#player-right-controller"
-                    ></a-entity>
-                </template>
-
-                <template data-selector=".Neck">
-                    <a-entity>
-                        <a-entity class="nametag" visible="false" text ></a-entity>
-                    </a-entity>
-                </template>
+            <template data-name="Neck">
+              <a-entity>
+                <a-entity class="nametag" visible="false" text ></a-entity>
+              </a-entity>
+            </template>
 
-                <template data-selector=".Head">
-                    <a-entity visible="false" bone-visibility></a-entity>
-                </template>
+            <template data-name="Head">
+              <a-entity visible="false" bone-visibility></a-entity>
+            </template>
 
-                <template data-selector=".LeftHand">
-                    <a-entity bone-visibility></a-entity>
-                </template>
+            <template data-name="LeftHand">
+              <a-entity bone-visibility></a-entity>
+            </template>
 
-                <template data-selector=".RightHand">
-                    <a-entity bone-visibility></a-entity>
-                </template>
+            <template data-name="RightHand">
+              <a-entity bone-visibility></a-entity>
+            </template>
 
             </a-entity>
         </a-entity>