diff --git a/package.json b/package.json
index 2d4901d3943dd6a8d752ced976608016fe96ce45..aa5b01f87564cb5083fc93e46e1ffd9eb5b9c16d 100644
--- a/package.json
+++ b/package.json
@@ -21,8 +21,6 @@
     "aframe-physics-system": "https://github.com/donmccurdy/aframe-physics-system",
     "aframe-teleport-controls": "https://github.com/netpro2k/aframe-teleport-controls#feature/teleport-origin",
     "aframe-xr": "github:brianpeiris/aframe-xr#3162aed",
-    "babel-plugin-react-intl": "^2.4.0",
-    "babel-plugin-transform-react-jsx-img-import": "^0.1.4",
     "classnames": "^2.2.5",
     "detect-browser": "^2.1.0",
     "event-target-shim": "^3.0.1",
@@ -48,7 +46,9 @@
     "babel-core": "^6.26.0",
     "babel-eslint": "^8.2.2",
     "babel-loader": "^7.1.3",
+    "babel-plugin-react-intl": "^2.4.0",
     "babel-plugin-transform-class-properties": "^6.24.1",
+    "babel-plugin-transform-react-jsx-img-import": "^0.1.4",
     "babel-preset-env": "^1.6.1",
     "babel-preset-react": "^6.24.1",
     "cross-env": "^5.1.3",
diff --git a/src/assets/environments/CliffVista_mesh.glb b/src/assets/environments/cliff_meeting_space/CliffVista_mesh.glb
similarity index 100%
rename from src/assets/environments/CliffVista_mesh.glb
rename to src/assets/environments/cliff_meeting_space/CliffVista_mesh.glb
diff --git a/src/assets/environments/FloorNav_mesh.glb b/src/assets/environments/cliff_meeting_space/FloorNav_mesh.glb
similarity index 100%
rename from src/assets/environments/FloorNav_mesh.glb
rename to src/assets/environments/cliff_meeting_space/FloorNav_mesh.glb
diff --git a/src/assets/environments/MeetingSpace1_mesh.glb b/src/assets/environments/cliff_meeting_space/MeetingSpace1_mesh.glb
similarity index 100%
rename from src/assets/environments/MeetingSpace1_mesh.glb
rename to src/assets/environments/cliff_meeting_space/MeetingSpace1_mesh.glb
diff --git a/src/assets/environments/OutdoorFacade_mesh.glb b/src/assets/environments/cliff_meeting_space/OutdoorFacade_mesh.glb
similarity index 100%
rename from src/assets/environments/OutdoorFacade_mesh.glb
rename to src/assets/environments/cliff_meeting_space/OutdoorFacade_mesh.glb
diff --git a/src/assets/environments/cliff_meeting_space/bundle.json.tpl b/src/assets/environments/cliff_meeting_space/bundle.json.tpl
new file mode 100644
index 0000000000000000000000000000000000000000..9a4eca63f5628750106b5d10693cc6ee6ea4856c
--- /dev/null
+++ b/src/assets/environments/cliff_meeting_space/bundle.json.tpl
@@ -0,0 +1,30 @@
+{
+  "name": "cliff_meeting_space",
+  "version": "0.1.0",
+  "layers": [
+    {
+      "name": "space-geometry",
+      "assets": [
+        { "name": "meeting-space", "src": "<%= require("./MeetingSpace1_mesh.glb") %>" }
+      ]
+    },
+    {
+      "name": "outdoor-geometry",
+      "assets": [
+        { "name": "outdoor-facade", "src": "<%= require("./OutdoorFacade_mesh.glb") %>" }
+      ]
+    },
+    {
+      "name": "collision",
+      "assets": [
+        { "name": "floor-nav", "src": "<%= require("./FloorNav_mesh.glb") %>" }
+      ]
+    },
+    {
+      "name": "cliff-geometry",
+      "assets": [
+        { "name": "cliff-vista", "src": "<%= require("./CliffVista_mesh.glb") %>" }
+      ]
+    }
+  ]
+}
diff --git a/src/components/gltf-bundle.js b/src/components/gltf-bundle.js
new file mode 100644
index 0000000000000000000000000000000000000000..614db2c1bb376843f2591b8aab89738ce8d2a671
--- /dev/null
+++ b/src/components/gltf-bundle.js
@@ -0,0 +1,30 @@
+AFRAME.registerComponent("gltf-bundle", {
+  schema: {
+    src: { default: "" }
+  },
+
+  init: async function() {
+    this._addGltfEntitiesForBundleJson = this._addGltfEntitiesForBundleJson.bind(this);
+
+    const res = await fetch(this.data.src);
+    const data = await res.json();
+    this._addGltfEntitiesForBundleJson(data);
+  },
+
+  _addGltfEntitiesForBundleJson: function(bundleJson) {
+    for (let i = 0; i < bundleJson.layers.length; i++) {
+      const layer = bundleJson.layers[i];
+
+      // TODO choose a proper asset based upon quality settings, etc. For now just take the first.
+      if (layer.assets.length > 1) {
+        throw `Unable to inflate bundle ${this.data.src} because multiple assets defined for layer ${layer.name}`;
+      }
+
+      const src = layer.assets[0].src;
+      const gltfEl = document.createElement("a-gltf-entity");
+      gltfEl.setAttribute("src", src);
+      gltfEl.setAttribute("position", "0 0 0");
+      this.el.appendChild(gltfEl);
+    }
+  }
+});
diff --git a/src/hub.html b/src/hub.html
index 0d21aafd9200849ec6aca411ee647aaadbf47984..62fb12552396d34b8f79ab4b3a5773969547f0c7 100644
--- a/src/hub.html
+++ b/src/hub.html
@@ -32,11 +32,6 @@
             ></a-progressive-asset>
             
             <a-asset-item id="watch-model" response-type="arraybuffer" src="./assets/hud/watch.glb"></a-asset-item>
-
-            <a-asset-item id="meeting-space1-mesh" response-type="arraybuffer" src="./assets/environments/MeetingSpace1_mesh.glb"></a-asset-item>
-            <a-asset-item id="outdoor-facade-mesh" response-type="arraybuffer" src="./assets/environments/OutdoorFacade_mesh.glb"></a-asset-item>
-            <a-asset-item id="floor-nav-mesh" response-type="arraybuffer" src="./assets/environments/FloorNav_mesh.glb"></a-asset-item>
-            <a-asset-item id="cliff-vista-mesh" response-type="arraybuffer" src="./assets/environments/CliffVista_mesh.glb"></a-asset-item>
             <a-asset-item id="interactable-duck" response-type="arraybuffer" src="./assets/interactables/duck/DuckyMesh.glb"></a-asset-item>
 
             <img id="water-normal-map" src="./assets/waternormals.jpg"></a-asset-item>
@@ -237,34 +232,7 @@
         ></a-entity>
 
         <!-- Environment -->
-        <a-gltf-entity
-            id="meeting-space"
-            src="#meeting-space1-mesh"
-            position="0 0 0"
-        ></a-gltf-entity>
-
-        <a-gltf-entity
-            id="outdoor-facade"
-            src="#outdoor-facade-mesh"
-            position="0 0 0"
-            xr="ar: false"
-        ></a-gltf-entity>
-
-        <a-gltf-entity
-            id="floor-nav"
-            src="#floor-nav-mesh"
-            visible="false"
-            position="0 0 0"
-            xr="ar: false"
-        ></a-gltf-entity>
-
-        <a-gltf-entity
-            id="cliff-vista"
-            src="#cliff-vista-mesh"
-            layers="reflection:true"
-            position="0 0 0"
-            xr="ar: false"
-        ></a-gltf-entity>
+        <a-entity id="environment-root" position="0 0 0"/>
 
         <a-entity id="skybox"
             id="skybox"
diff --git a/src/hub.js b/src/hub.js
index 8f8acd0f020e22d81c4951d7d4f1d2b4a96d0738..5e37231b03139ef460a1df49e567a317184790a5 100644
--- a/src/hub.js
+++ b/src/hub.js
@@ -39,6 +39,7 @@ import "./components/animated-robot-hands";
 import "./components/hide-when-quality";
 import "./components/animation-mixer";
 import "./components/loop-animation";
+import "./components/gltf-bundle";
 
 import ReactDOM from "react-dom";
 import React from "react";
@@ -243,6 +244,13 @@ const onReady = () => {
   const scene = document.querySelector("a-scene");
   document.querySelector("a-scene canvas").classList.add("blurred");
   window.APP.scene = scene;
+
+  // If ?room is set, this is `yarn start`, so just use a default environment. Otherwise use Reticulum API.
+  if (qs.room) {
+    const environmentRoot = document.querySelector("#environment-root");
+    environmentRoot.setAttribute("gltf-bundle", "src: /assets/environments/cliff_meeting_space/bundle.json")
+  }
+
   mountUI(scene);
 };
 
diff --git a/webpack.config.js b/webpack.config.js
index fdb7c6b1ec4eb2c0535015a2dd892aad2f8117b5..8476cd91724392dea8deb37cf713c4fea6e74fa7 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -3,6 +3,7 @@ require("dotenv").config();
 
 const fs = require("fs");
 const path = require("path");
+const glob = require("glob");
 const selfsigned = require("selfsigned");
 const webpack = require("webpack");
 const HTMLWebpackPlugin = require("html-webpack-plugin");
@@ -195,6 +196,15 @@ const config = {
       chunks: ["hub"],
       inject: "head"
     }),
+    // Build the GLTF asset bundle json files
+    ...glob.sync("src/assets/**/*.tpl").map(
+      f =>
+        new HTMLWebpackPlugin({
+          filename: f.replace(".tpl", "").replace("src/", ""),
+          template: path.join(...[__dirname, ...f.split("/")]),
+          chunks: []
+        })
+    ),
     // Extract required css and add a content hash.
     new ExtractTextPlugin("assets/stylesheets/[name]-[contenthash].css", {
       disable: process.env.NODE_ENV !== "production"
diff --git a/yarn.lock b/yarn.lock
index 7a0884a0ae0198f5d8f079ca59c2141f478966b8..161141df1c8f07aa9c7ffd6a956fc879159e3739 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -411,6 +411,16 @@ assert@^1.1.1, assert@^1.4.0:
   dependencies:
     util "0.10.3"
 
+assets-webpack-plugin@^3.5.1:
+  version "3.5.1"
+  resolved "https://registry.yarnpkg.com/assets-webpack-plugin/-/assets-webpack-plugin-3.5.1.tgz#931ce0d66d42e88ed5e7f18d65522943c57a387d"
+  dependencies:
+    camelcase "^1.2.1"
+    escape-string-regexp "^1.0.3"
+    lodash.assign "^3.2.0"
+    lodash.merge "^3.3.2"
+    mkdirp "^0.5.1"
+
 assign-symbols@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
@@ -1680,6 +1690,10 @@ camelcase-keys@^2.0.0:
     camelcase "^2.0.0"
     map-obj "^1.0.0"
 
+camelcase@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39"
+
 camelcase@^2.0.0:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
@@ -2815,7 +2829,7 @@ escape-html@^1.0.3, escape-html@~1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
 
-escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
+escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.3, escape-string-regexp@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
 
@@ -4698,6 +4712,57 @@ locate-path@^2.0.0:
     p-locate "^2.0.0"
     path-exists "^3.0.0"
 
+lodash._arraycopy@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz#76e7b7c1f1fb92547374878a562ed06a3e50f6e1"
+
+lodash._arrayeach@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz#bab156b2a90d3f1bbd5c653403349e5e5933ef9e"
+
+lodash._baseassign@^3.0.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e"
+  dependencies:
+    lodash._basecopy "^3.0.0"
+    lodash.keys "^3.0.0"
+
+lodash._basecopy@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36"
+
+lodash._basefor@^3.0.0:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/lodash._basefor/-/lodash._basefor-3.0.3.tgz#7550b4e9218ef09fad24343b612021c79b4c20c2"
+
+lodash._bindcallback@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e"
+
+lodash._createassigner@^3.0.0:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz#838a5bae2fdaca63ac22dee8e19fa4e6d6970b11"
+  dependencies:
+    lodash._bindcallback "^3.0.0"
+    lodash._isiterateecall "^3.0.0"
+    lodash.restparam "^3.0.0"
+
+lodash._getnative@^3.0.0:
+  version "3.9.1"
+  resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
+
+lodash._isiterateecall@^3.0.0:
+  version "3.0.9"
+  resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c"
+
+lodash.assign@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-3.2.0.tgz#3ce9f0234b4b2223e296b8fa0ac1fee8ebca64fa"
+  dependencies:
+    lodash._baseassign "^3.0.0"
+    lodash._createassigner "^3.0.0"
+    lodash.keys "^3.0.0"
+
 lodash.assign@^4.2.0:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
@@ -4714,14 +4779,49 @@ lodash.endswith@^4.2.1:
   version "4.2.1"
   resolved "https://registry.yarnpkg.com/lodash.endswith/-/lodash.endswith-4.2.1.tgz#fed59ac1738ed3e236edd7064ec456448b37bc09"
 
+lodash.isarguments@^3.0.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
+
+lodash.isarray@^3.0.0:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
+
 lodash.isfunction@^3.0.8:
   version "3.0.9"
   resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051"
 
+lodash.isplainobject@^3.0.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-3.2.0.tgz#9a8238ae16b200432960cd7346512d0123fbf4c5"
+  dependencies:
+    lodash._basefor "^3.0.0"
+    lodash.isarguments "^3.0.0"
+    lodash.keysin "^3.0.0"
+
 lodash.isstring@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
 
+lodash.istypedarray@^3.0.0:
+  version "3.0.6"
+  resolved "https://registry.yarnpkg.com/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz#c9a477498607501d8e8494d283b87c39281cef62"
+
+lodash.keys@^3.0.0:
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a"
+  dependencies:
+    lodash._getnative "^3.0.0"
+    lodash.isarguments "^3.0.0"
+    lodash.isarray "^3.0.0"
+
+lodash.keysin@^3.0.0:
+  version "3.0.8"
+  resolved "https://registry.yarnpkg.com/lodash.keysin/-/lodash.keysin-3.0.8.tgz#22c4493ebbedb1427962a54b445b2c8a767fb47f"
+  dependencies:
+    lodash.isarguments "^3.0.0"
+    lodash.isarray "^3.0.0"
+
 lodash.memoize@^4.1.2:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
@@ -4730,10 +4830,30 @@ lodash.memoize@~3.0.3:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f"
 
+lodash.merge@^3.3.2:
+  version "3.3.2"
+  resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-3.3.2.tgz#0d90d93ed637b1878437bb3e21601260d7afe994"
+  dependencies:
+    lodash._arraycopy "^3.0.0"
+    lodash._arrayeach "^3.0.0"
+    lodash._createassigner "^3.0.0"
+    lodash._getnative "^3.0.0"
+    lodash.isarguments "^3.0.0"
+    lodash.isarray "^3.0.0"
+    lodash.isplainobject "^3.0.0"
+    lodash.istypedarray "^3.0.0"
+    lodash.keys "^3.0.0"
+    lodash.keysin "^3.0.0"
+    lodash.toplainobject "^3.0.0"
+
 lodash.mergewith@^4.6.0:
   version "4.6.1"
   resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927"
 
+lodash.restparam@^3.0.0:
+  version "3.6.1"
+  resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
+
 lodash.startswith@^4.2.1:
   version "4.2.1"
   resolved "https://registry.yarnpkg.com/lodash.startswith/-/lodash.startswith-4.2.1.tgz#c598c4adce188a27e53145731cdc6c0e7177600c"
@@ -4742,6 +4862,13 @@ lodash.tail@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664"
 
+lodash.toplainobject@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/lodash.toplainobject/-/lodash.toplainobject-3.0.0.tgz#28790ad942d293d78aa663a07ecf7f52ca04198d"
+  dependencies:
+    lodash._basecopy "^3.0.0"
+    lodash.keysin "^3.0.0"
+
 lodash.uniq@^4.5.0:
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"