diff --git a/scripts/bot/run-bot.js b/scripts/bot/run-bot.js
old mode 100644
new mode 100755
index 934ea7148387cf99cbb498ad7c99b36c4af4ba5a..dc3cb0160099bb1e437af9c8ea3065e7eb84c0aa
--- a/scripts/bot/run-bot.js
+++ b/scripts/bot/run-bot.js
@@ -2,7 +2,6 @@
 const doc = `
 Usage:
     ./run-bot.js [options]
-
 Options:
     -u --url=<url>    URL
     -o --host=<host>  Hubs host if URL is not specified [default: localhost:8080]
diff --git a/src/components/audio-feedback.js b/src/components/audio-feedback.js
index 7edf3ec3f654eb9ee7b45c35c58d2afee2c56373..aa7182fa7329145078a1ff27270700164444efff 100644
--- a/src/components/audio-feedback.js
+++ b/src/components/audio-feedback.js
@@ -4,8 +4,8 @@
  * @component networked-audio-analyser
  */
 AFRAME.registerComponent("networked-audio-analyser", {
-  schema: {},
   async init() {
+    this.volume = 0;
     this.el.addEventListener("sound-source-set", event => {
       const ctx = THREE.AudioContext.getContext();
       this.analyser = ctx.createAnalyser();
@@ -25,10 +25,6 @@ AFRAME.registerComponent("networked-audio-analyser", {
       sum += this.levels[i];
     }
     this.volume = sum / this.levels.length;
-    this.el.emit("audioFrequencyChange", {
-      volume: this.volume,
-      levels: this.levels
-    });
   }
 });
 
@@ -37,24 +33,12 @@ AFRAME.registerComponent("networked-audio-analyser", {
  * @component matcolor-audio-feedback
  */
 AFRAME.registerComponent("matcolor-audio-feedback", {
-  schema: {
-    analyserSrc: { type: "selector" }
-  },
-  init: function() {
-    this.onAudioFrequencyChange = this.onAudioFrequencyChange.bind(this);
-  },
+  tick() {
+    const audioAnalyser = this.el.components["networked-audio-analyser"];
 
-  play() {
-    (this.data.analyserSrc || this.el).addEventListener("audioFrequencyChange", this.onAudioFrequencyChange);
-  },
+    if (!audioAnalyser || !this.mat) return;
 
-  pause() {
-    (this.data.analyserSrc || this.el).removeEventListener("audioFrequencyChange", this.onAudioFrequencyChange);
-  },
-
-  onAudioFrequencyChange(e) {
-    if (!this.mat) return;
-    this.object3D.mesh.color.setScalar(1 + e.detail.volume / 255 * 2);
+    this.object3D.mesh.color.setScalar(1 + audioAnalyser.volume / 255 * 2);
   }
 });
 
@@ -65,29 +49,21 @@ AFRAME.registerComponent("matcolor-audio-feedback", {
  */
 AFRAME.registerComponent("scale-audio-feedback", {
   schema: {
-    analyserSrc: { type: "selector" },
-
     minScale: { default: 1 },
     maxScale: { default: 2 }
   },
 
-  init() {
-    this.onAudioFrequencyChange = this.onAudioFrequencyChange.bind(this);
-  },
-
-  play() {
-    (this.data.analyserSrc || this.el).addEventListener("audioFrequencyChange", this.onAudioFrequencyChange);
-  },
-
-  pause() {
-    (this.data.analyserSrc || this.el).removeEventListener("audioFrequencyChange", this.onAudioFrequencyChange);
-  },
-
-  onAudioFrequencyChange(e) {
+  tick() {
     // 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);
+
+    const audioAnalyser = this.el.components["networked-audio-analyser"];
+
+    if (!audioAnalyser) return;
+
+    this.el.object3D.scale.setScalar(minScale + (maxScale - minScale) * audioAnalyser.volume / 255);
   }
 });
diff --git a/src/components/character-controller.js b/src/components/character-controller.js
index 27cf940f485994ac83d1a727e41aa23d32464e05..f32debe666107ccfb6144f94e7ca5afe3f9f4168 100644
--- a/src/components/character-controller.js
+++ b/src/components/character-controller.js
@@ -125,24 +125,24 @@ AFRAME.registerComponent("character-controller", {
       yawMatrix.makeRotationAxis(rotationAxis, rotationDelta);
 
       // Translate to middle of playspace (player rig)
-      root.matrix.multiplyMatrices(transInv, root.matrix);
+      root.matrix.premultiply(transInv);
       // Zero playspace (player rig) rotation
-      root.matrix.multiplyMatrices(rotationInvMatrix, root.matrix);
+      root.matrix.premultiply(rotationInvMatrix);
       // Zero pivot (camera/head) rotation
-      root.matrix.multiplyMatrices(pivotRotationInvMatrix, root.matrix);
+      root.matrix.premultiply(pivotRotationInvMatrix);
       // Apply joystick translation
-      root.matrix.multiplyMatrices(move, root.matrix);
+      root.matrix.premultiply(move);
       // Apply joystick yaw rotation
-      root.matrix.multiplyMatrices(yawMatrix, root.matrix);
+      root.matrix.premultiply(yawMatrix);
       // Apply snap rotation if necessary
-      root.matrix.multiplyMatrices(this.pendingSnapRotationMatrix, root.matrix);
+      root.matrix.premultiply(this.pendingSnapRotationMatrix);
       // Reapply pivot (camera/head) rotation
-      root.matrix.multiplyMatrices(pivotRotationMatrix, root.matrix);
+      root.matrix.premultiply(pivotRotationMatrix);
       // Reapply playspace (player rig) rotation
-      root.matrix.multiplyMatrices(rotationMatrix, root.matrix);
+      root.matrix.premultiply(rotationMatrix);
       // Reapply playspace (player rig) translation
-      root.matrix.multiplyMatrices(trans, root.matrix);
-
+      root.matrix.premultiply(trans);
+      // update pos/rot/scale
       root.matrix.decompose(root.position, root.quaternion, root.scale);
 
       // TODO: the above matrix trnsfomraitons introduce some floating point errors in scale, this reverts them to
diff --git a/src/systems/personal-space-bubble.js b/src/systems/personal-space-bubble.js
index 0ba3cee5d48a1b5095031db89f9fa2cf6e490429..238c0b63f70c06502350b83dd354cbb256c755bf 100644
--- a/src/systems/personal-space-bubble.js
+++ b/src/systems/personal-space-bubble.js
@@ -74,13 +74,10 @@ AFRAME.registerSystem("personal-space-bubble", {
   tick() {
     if (!this.data.enabled) return;
 
-    // Update matrix positions once for each space bubble and space invader
-    for (let i = 0; i < this.bubbles.length; i++) {
-      this.bubbles[i].el.object3D.updateMatrixWorld(true);
-    }
+    // precondition for this stuff -- the bubbles and invaders need updated world matrices.
+    // right now this is satisfied because we update the world matrices in the character controller
 
     for (let i = 0; i < this.invaders.length; i++) {
-      this.invaders[i].el.object3D.updateMatrixWorld(true);
       this.invaders[i].setInvading(false);
     }