diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp
index 03014bae6a..11e4d533fb 100644
--- a/assignment-client/src/entities/EntityTreeSendThread.cpp
+++ b/assignment-client/src/entities/EntityTreeSendThread.cpp
@@ -175,7 +175,7 @@ bool EntityTreeSendThread::addAncestorsToExtraFlaggedEntities(const QUuid& filte
         return parentWasNew || ancestorsWereNew;
     }
 
-    // since we didn't have a parent niether of our parents or ancestors could be new additions
+    // since we didn't have a parent, neither of our parents or ancestors could be new additions
     return false;
 }
 
@@ -204,7 +204,9 @@ bool EntityTreeSendThread::addDescendantsToExtraFlaggedEntities(const QUuid& fil
     return hasNewChild || hasNewDescendants;
 }
 
-void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum) {
+void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset, 
+        bool usesViewFrustum) {
+
     DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root, lodLevelOffset, usesViewFrustum);
     // there are three types of traversal:
     //
@@ -423,12 +425,19 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream
     uint64_t sendTime = usecTimestampNow();
     auto nodeData = static_cast<OctreeQueryNode*>(params.nodeData);
     nodeData->stats.encodeStarted();
+    auto entityNode = _node.toStrongRef();
+    auto entityNodeData = static_cast<EntityNodeData*>(entityNode->getLinkedData());
     while(!_sendQueue.empty()) {
         PrioritizedEntity queuedItem = _sendQueue.top();
         EntityItemPointer entity = queuedItem.getEntity();
         if (entity) {
             // Only send entities that match the jsonFilters, but keep track of everything we've tried to send so we don't try to send it again
-            if (entity->matchesJSONFilters(jsonFilters)) {
+            bool entityMatchesFilters = entity->matchesJSONFilters(jsonFilters);
+            if (entityMatchesFilters || entityNodeData->isEntityFlaggedAsExtra(entity->getID())) {
+                if (!jsonFilters.isEmpty() && entityMatchesFilters) {
+                    // Record explicitly filtered-in entity so that extra entities can be flagged.
+                    entityNodeData->insertSentFilteredEntity(entity->getID());
+                }
                 OctreeElement::AppendState appendEntityState = entity->appendEntityData(&_packetData, params, _extraEncodeData);
 
                 if (appendEntityState != OctreeElement::COMPLETED) {
diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h
index 49901491ff..a96a18494d 100644
--- a/assignment-client/src/entities/EntityTreeSendThread.h
+++ b/assignment-client/src/entities/EntityTreeSendThread.h
@@ -38,7 +38,8 @@ private:
     bool addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData);
     bool addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData);
 
-    void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum);
+    void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, 
+        bool usesViewFrustum);
     bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) override;
 
     void preDistributionProcessing() override;
diff --git a/cmake/externals/wasapi/CMakeLists.txt b/cmake/externals/wasapi/CMakeLists.txt
index 4437024962..4c0ffaf88f 100644
--- a/cmake/externals/wasapi/CMakeLists.txt
+++ b/cmake/externals/wasapi/CMakeLists.txt
@@ -6,8 +6,8 @@ if (WIN32)
   include(ExternalProject)
   ExternalProject_Add(
     ${EXTERNAL_NAME}
-    URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi9.zip
-    URL_MD5 94f4765bdbcd53cd099f349ae031e769
+    URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi10.zip
+    URL_MD5 4f40e49715a420fb67b45b9cee19052c
     CONFIGURE_COMMAND ""
     BUILD_COMMAND ""
     INSTALL_COMMAND ""
diff --git a/domain-server/resources/web/content/index.shtml b/domain-server/resources/web/content/index.shtml
index e1ba5499b6..0e48c1eff8 100644
--- a/domain-server/resources/web/content/index.shtml
+++ b/domain-server/resources/web/content/index.shtml
@@ -19,12 +19,13 @@
               Upload an entities file (e.g.: models.json.gz) to replace the content of this domain.<br>
               Note: <strong>Your domain's content will be replaced by the content you upload</strong>, but the backup files of your domain's content will not immediately be changed.
             </p>
-            <p>
-              If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:<br>
-              <pre>C:/Users/[username]/AppData/Roaming/High Fidelity/assignment-client/entities/models.json.gz</pre>
-              <pre>/Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz</pre>
-              <pre>/home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz</pre>
-            </p>
+            <p>If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:</p>
+            <label class="control-label">Windows</label>
+            <pre>C:/Users/[username]/AppData/Roaming/High Fidelity/assignment-client/entities/models.json.gz</pre>
+            <label class="control-label">OSX</label>
+            <pre>/Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz</pre>
+            <label class="control-label">Linux</label>
+            <pre>/home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz</pre>
             <br>
             <input type="file" name="entities-file" class="form-control-file" accept=".json, .gz">
             <br>
diff --git a/domain-server/resources/web/js/shared.js b/domain-server/resources/web/js/shared.js
index a5dd522a53..66159209ea 100644
--- a/domain-server/resources/web/js/shared.js
+++ b/domain-server/resources/web/js/shared.js
@@ -146,187 +146,256 @@ function sendUpdatePlaceRequest(id, path, domainID, clearDomainID, onSuccess, on
   });
 }
 
+var pendingDomainRequest = null;
+function getDomainFromAPI(callback) {
+  if (pendingDomainRequest !== null) {
+    pendingDomainRequest.success(callback);
+    pendingDomainRequest.error(function() { callback({ status: 'fail' }) });
+    return pendingDomainRequest;
+  }
+
+  if (callback === undefined) {
+    callback = function() {};
+  }
+
+  var domainID = Settings.data.values.metaverse.id;
+  if (domainID === null || domainID === undefined || domainID === '') {
+    callback({ status: 'fail' });
+    return null;
+  }
+
+  pendingDomainRequest = $.ajax({
+    url: "/api/domains/" + domainID,
+    dataType: 'json',
+    success: function(data) {
+      pendingDomainRequest = null;
+
+      if (data.status === 'success') {
+        DomainInfo = data.domain;
+      } else {
+        DomainInfo = null;
+      }
+      callback(data);
+    },
+    error: function() {
+      pendingDomainRequest = null;
+
+      DomainInfo = null;
+      callback({ status: 'fail' });
+    }
+  });
+
+  return pendingDomainRequest;
+}
+
 function chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAdded) {
   if (accessToken) {
 
     var loadingDialog = showLoadingDialog(Strings.ADD_PLACE_LOADING_DIALOG);
 
-    $.ajax("/api/places", {
-      dataType: 'json',
-      jsonp: false,
-      success: function(data) {
-        if (data.status == 'success') {
-          var modal_buttons = {
-            cancel: {
-              label: Strings.ADD_PLACE_CANCEL_BUTTON,
-              className: 'add-place-cancel-button btn-default'
-            }
-          };
-
-          var dialog;
-          var modal_body;
-
-          if (data.data.places.length) {
-            var places_by_id = {};
-
-            modal_body = $('<div>');
-
-            modal_body.append($("<p>Choose a place name that you own or <a href='" + URLs.METAVERSE_URL + "/user/places' target='_blank'>register a new place name</a></p>"));
-
-            var currentDomainIDType = getCurrentDomainIDType();
-            if (currentDomainIDType === DOMAIN_ID_TYPE_TEMP) {
-              var warning = "<div class='domain-loading-error alert alert-warning'>";
-              warning += "If you choose a place name it will replace your current temporary place name.";
-              warning += "</div>";
-              modal_body.append(warning);
-            }
-
-            // setup a select box for the returned places
-            modal_body.append($("<label for='place-name-select'>Places</label>"));
-            place_select = $("<select id='place-name-select' class='form-control'></select>");
-            _.each(data.data.places, function(place) {
-              places_by_id[place.id] = place;
-              place_select.append("<option value='" + place.id + "'>" + place.name + "</option>");
-            })
-            modal_body.append(place_select);
-            modal_body.append($("<p id='place-name-warning' class='warning-text' style='display: none'>This place name already points to a place or path. Saving this would overwrite the previous settings associated with it.</p>"));
-
-            if (forcePathTo === undefined || forcePathTo === null) {
-              var path = "<div class='form-group'>";
-              path += "<label for='place-path-input' class='control-label'>Path</label>";
-              path += "<input type='text' id='place-path-input' class='form-control' value='/'>";
-              path += "</div>";
-              modal_body.append($(path));
-            }
-
-            var place_select = modal_body.find("#place-name-select")
-            place_select.change(function(ev) {
-              var warning = modal_body.find("#place-name-warning");
-              var place = places_by_id[$(this).val()];
-              if (place === undefined || place.pointee === null) {
-                warning.hide();
-              } else {
-                warning.show();
+    function loadPlaces() {
+      $.ajax("/api/places", {
+        dataType: 'json',
+        jsonp: false,
+        success: function(data) {
+          if (data.status == 'success') {
+            var modal_buttons = {
+              cancel: {
+                label: Strings.ADD_PLACE_CANCEL_BUTTON,
+                className: 'add-place-cancel-button btn-default'
               }
-            });
-            place_select.trigger('change');
+            };
 
-            modal_buttons["success"] = {
-              label: Strings.ADD_PLACE_CONFIRM_BUTTON,
-              className: 'add-place-confirm-button btn btn-primary',
-              callback: function() {
-                var placeID = $('#place-name-select').val();
-                // set the place ID on the form
-                $(Settings.place_ID_SELECTOR).val(placeID).change();
+            var dialog;
+            var modal_body;
 
-                if (forcePathTo === undefined || forcePathTo === null) {
-                  var placePath = $('#place-path-input').val();
+            if (data.data.places.length) {
+              var places_by_id = {};
+
+              modal_body = $('<div>');
+
+              modal_body.append($("<p>Choose a place name that you own or <a href='" + URLs.METAVERSE_URL + "/user/places' target='_blank'>register a new place name</a></p>"));
+
+              var currentDomainIDType = getCurrentDomainIDType();
+              if (currentDomainIDType === DOMAIN_ID_TYPE_TEMP) {
+                var warning = "<div class='domain-loading-error alert alert-warning'>";
+                warning += "If you choose a place name it will replace your current temporary place name.";
+                warning += "</div>";
+                modal_body.append(warning);
+              }
+
+              // setup a select box for the returned places
+              modal_body.append($("<label for='place-name-select'>Places</label>"));
+              place_select = $("<select id='place-name-select' class='form-control'></select>");
+              _.each(data.data.places, function(place) {
+                places_by_id[place.id] = place;
+                place_select.append("<option value='" + place.id + "'>" + place.name + "</option>");
+              })
+              modal_body.append(place_select);
+              modal_body.append($("<p id='place-name-warning' class='warning-text' style='display: none'>This place name already points to a place or path. Saving this would overwrite the previous settings associated with it.</p>"));
+
+              if (forcePathTo === undefined || forcePathTo === null) {
+                var path = "<div class='form-group'>";
+                path += "<label for='place-path-input' class='control-label'>Path</label>";
+                path += "<input type='text' id='place-path-input' class='form-control' value='/'>";
+                path += "</div>";
+                modal_body.append($(path));
+              }
+
+              var place_select = modal_body.find("#place-name-select")
+              place_select.change(function(ev) {
+                var warning = modal_body.find("#place-name-warning");
+                var place = places_by_id[$(this).val()];
+                if (place === undefined || place.pointee === null) {
+                  warning.hide();
                 } else {
-                  var placePath = forcePathTo;
+                  warning.show();
                 }
+              });
+              place_select.trigger('change');
 
-                $('.add-place-confirm-button').attr('disabled', 'disabled');
-                $('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON_PENDING);
-                $('.add-place-cancel-button').attr('disabled', 'disabled');
+              modal_buttons["success"] = {
+                label: Strings.ADD_PLACE_CONFIRM_BUTTON,
+                className: 'add-place-confirm-button btn btn-primary',
+                callback: function() {
+                  var placeID = $('#place-name-select').val();
+                  // set the place ID on the form
+                  $(Settings.place_ID_SELECTOR).val(placeID).change();
 
-                function finalizeSaveDomainID(domainID) {
-                  var jsonSettings = {
-                    metaverse: {
-                      id: domainID
+                  if (forcePathTo === undefined || forcePathTo === null) {
+                    var placePath = $('#place-path-input').val();
+                  } else {
+                    var placePath = forcePathTo;
+                  }
+
+                  $('.add-place-confirm-button').attr('disabled', 'disabled');
+                  $('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON_PENDING);
+                  $('.add-place-cancel-button').attr('disabled', 'disabled');
+
+                  function finalizeSaveDomainID(domainID) {
+                    var jsonSettings = {
+                      metaverse: {
+                        id: domainID
+                      }
+                    }
+                    var dialog = showLoadingDialog("Waiting for Domain Server to restart...");
+                    $.ajax('/settings.json', {
+                      data: JSON.stringify(jsonSettings),
+                      contentType: 'application/json',
+                      type: 'POST'
+                    }).done(function(data) {
+                      if (data.status == "success") {
+                        waitForDomainServerRestart(function() {
+                          dialog.modal('hide');
+                          if (onSuccessfullyAdded) {
+                            onSuccessfullyAdded(places_by_id[placeID].name, domainID);
+                          }
+                        });
+                      } else {
+                        bootbox.alert("Failed to add place");
+                      }
+                    }).fail(function() {
+                      bootbox.alert("Failed to add place");
+                    });
+                  }
+
+                  // If domainID is not specified, the current domain id will be used.
+                  function finishSettingUpPlace(domainID) {
+                    sendUpdatePlaceRequest(
+                      placeID,
+                      placePath,
+                      domainID,
+                      false,
+                      function(data) {
+                        dialog.modal('hide')
+                        if (domainID) {
+                          $(Settings.DOMAIN_ID_SELECTOR).val(domainID).change();
+                          finalizeSaveDomainID(domainID);
+                        } else {
+                          if (onSuccessfullyAdded) {
+                            onSuccessfullyAdded(places_by_id[placeID].name);
+                          }
+                        }
+                      },
+                      function(data) {
+                        $('.add-place-confirm-button').removeAttr('disabled');
+                        $('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON);
+                        $('.add-place-cancel-button').removeAttr('disabled');
+                        bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR);
+                      }
+                    );
+                  }
+
+                  function maybeCreateNewDomainID() {
+                    console.log("Maybe creating domain id", currentDomainIDType)
+                    if (currentDomainIDType === DOMAIN_ID_TYPE_FULL) {
+                      finishSettingUpPlace();
+                    } else {
+                      sendCreateDomainRequest(function(domainID) {
+                        console.log("Created domain", domainID);
+                        finishSettingUpPlace(domainID);
+                      }, function() {
+                        $('.add-place-confirm-button').removeAttr('disabled');
+                        $('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON);
+                        $('.add-place-cancel-button').removeAttr('disabled');
+                        bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR);
+                        bootbox.alert("FAIL");
+                      });
                     }
                   }
-                  var dialog = showLoadingDialog("Waiting for Domain Server to restart...");
-                  $.ajax('/settings.json', {
-                    data: JSON.stringify(jsonSettings),
-                    contentType: 'application/json',
-                    type: 'POST'
-                  }).done(function(data) {
-                    if (data.status == "success") {
-                      waitForDomainServerRestart(function() {
-                        dialog.modal('hide');
-                        if (onSuccessfullyAdded) {
-                          onSuccessfullyAdded(places_by_id[placeID].name, domainID);
-                        }
-                      });
-                    } else {
-                      bootbox.alert("Failed to add place");
-                    }
-                  }).fail(function() {
-                    bootbox.alert("Failed to add place");
-                  });
-                }
 
-                function finishSettingUpPlace(domainID) {
-                  sendUpdatePlaceRequest(
-                    placeID,
-                    placePath,
-                    domainID,
-                    false,
-                    function(data) {
-                      $(Settings.DOMAIN_ID_SELECTOR).val(domainID).change();
-                      dialog.modal('hide')
-                      if (domainID) {
-                        finalizeSaveDomainID(domainID);
-                      } else {
-                        if (onSuccessfullyAdded) {
-                          onSuccessfullyAdded(places_by_id[placeID].name);
-                        }
-                      }
-                    },
-                    function(data) {
-                      $('.add-place-confirm-button').removeAttr('disabled');
-                      $('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON);
-                      $('.add-place-cancel-button').removeAttr('disabled');
-                      bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR);
-                    }
-                  );
-                }
+                  maybeCreateNewDomainID();
 
-                if (currentDomainIDType === DOMAIN_ID_TYPE_FULL) {
-                  finishSettingUpPlace();
-                } else {
-                  sendCreateDomainRequest(function(domainID) {
-                    console.log("Created domain", domainID);
-                    finishSettingUpPlace(domainID);
-                  }, function() {
-                    $('.add-place-confirm-button').removeAttr('disabled');
-                    $('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON);
-                    $('.add-place-cancel-button').removeAttr('disabled');
-                    bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR);
-                    bootbox.alert("FAIL");
-                  });
+                  return false;
                 }
-
-                return false;
               }
+            } else {
+              modal_buttons["success"] = {
+                label: Strings.ADD_PLACE_NO_PLACES_BUTTON,
+                callback: function() {
+                  window.open(URLs.METAVERSE_URL + "/user/places", '_blank');
+                }
+              }
+              modal_body = Strings.ADD_PLACE_NO_PLACES_MESSAGE;
             }
+
+            dialog = bootbox.dialog({
+              title: Strings.ADD_PLACE_TITLE,
+              message: modal_body,
+              closeButton: false,
+              buttons: modal_buttons
+            });
           } else {
-            modal_buttons["success"] = {
-              label: Strings.ADD_PLACE_NO_PLACES_BUTTON,
-              callback: function() {
-                window.open(URLs.METAVERSE_URL + "/user/places", '_blank');
-              }
-            }
-            modal_body = Strings.ADD_PLACE_NO_PLACES_MESSAGE;
+            bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR);
           }
-
-          dialog = bootbox.dialog({
-            title: Strings.ADD_PLACE_TITLE,
-            message: modal_body,
-            closeButton: false,
-            buttons: modal_buttons
-          });
-        } else {
+        },
+        error: function() {
           bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR);
+        },
+        complete: function() {
+          loadingDialog.modal('hide');
         }
-      },
-      error: function() {
-        bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR);
-      },
-      complete: function() {
-        loadingDialog.modal('hide');
-      }
-    });
+      });
+    }
+
+    var domainType = getCurrentDomainIDType();
+    if (domainType !== DOMAIN_ID_TYPE_UNKNOWN) {
+      loadPlaces();
+    } else {
+      getDomainFromAPI(function(data) {
+        if (data.status === 'success') {
+          var domainType = getCurrentDomainIDType();
+          loadPlaces();
+        } else {
+          loadingDialog.modal('hide');
+          bootbox.confirm("We were not able to load your domain information from the Metaverse. Would you like to retry?", function(response) {
+            if (response) {
+              chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAdded);
+            }
+          });
+        }
+      })
+    }
 
   } else {
     bootbox.alert({
diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js
index 1224b724da..7f99b367a3 100644
--- a/domain-server/resources/web/settings/js/settings.js
+++ b/domain-server/resources/web/settings/js/settings.js
@@ -980,20 +980,6 @@ function placeTableRowForPlaceObject(place) {
   return placeTableRow(place.name, placePathOrIndex, false, place.id);
 }
 
-function getDomainFromAPI(callback) {
-  var domainID = Settings.data.values.metaverse.id;
-  $.ajax({
-    url: "/api/domains/" + domainID,
-    dataType: 'json',
-    success: function(data) {
-      callback(data);
-    },
-    error: function() {
-      callback({ status: 'fail' });
-    }
-  });
-}
-
 function reloadDomainInfo() {
   $('#' + Settings.PLACES_TABLE_ID + " tbody tr").not('.headers').remove();
 
@@ -1010,7 +996,6 @@ function reloadDomainInfo() {
     // check if we have owner_places (for a real domain) or a name (for a temporary domain)
     if (data.status == "success") {
       $('.domain-loading-hide').show();
-      DomainInfo = data.domain;
       if (data.domain.owner_places) {
         // add a table row for each of these names
         _.each(data.domain.owner_places, function(place){
@@ -1043,7 +1028,6 @@ function reloadDomainInfo() {
       appendAddButtonToPlacesTable();
 
     } else {
-      DomainInfo = null;
       $('.domain-loading-error').show();
     }
   })
diff --git a/domain-server/resources/web/wizard/js/wizard.js b/domain-server/resources/web/wizard/js/wizard.js
index 24a82402a6..1af3f305b7 100644
--- a/domain-server/resources/web/wizard/js/wizard.js
+++ b/domain-server/resources/web/wizard/js/wizard.js
@@ -58,6 +58,7 @@ $(document).ready(function(){
 
   reloadSettings(function(success) {
     if (success) {
+      getDomainFromAPI();
       setupWizardSteps();
       updatePlaceNameDisplay();
       updateUsernameDisplay();
diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp
index 43262a1a81..6c50e5245d 100644
--- a/domain-server/src/DomainServerSettingsManager.cpp
+++ b/domain-server/src/DomainServerSettingsManager.cpp
@@ -29,6 +29,7 @@
 #include <NLPacketList.h>
 #include <NumericalConstants.h>
 #include <SettingHandle.h>
+#include <SettingHelpers.h>
 #include <AvatarData.h> //for KillAvatarReason
 #include <FingerprintUtils.h>
 #include "DomainServerNodeData.h"
@@ -43,12 +44,7 @@ const QString DESCRIPTION_COLUMNS_KEY = "columns";
 
 const QString SETTINGS_VIEWPOINT_KEY = "viewpoint";
 
-static Setting::Handle<double> JSON_SETTING_VERSION("json-settings/version", 0.0);
-
-DomainServerSettingsManager::DomainServerSettingsManager() :
-    _descriptionArray(),
-    _configMap()
-{
+DomainServerSettingsManager::DomainServerSettingsManager() {
     // load the description object from the settings description
     QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH);
     descriptionFile.open(QIODevice::ReadOnly);
@@ -100,12 +96,34 @@ void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer<Re
 void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) {
     _argumentList = argumentList;
 
-    // after 1.7 we no longer use the master or merged configs - this is kept in place for migration
-    _configMap.loadMasterAndUserConfig(_argumentList);
+    _configMap.loadConfig(_argumentList);
+
+    static const auto VERSION_SETTINGS_KEYPATH = "version";
+    QVariant* versionVariant = _configMap.valueForKeyPath(VERSION_SETTINGS_KEYPATH);
+
+    if (!versionVariant) {
+        versionVariant = _configMap.valueForKeyPath(VERSION_SETTINGS_KEYPATH, true);
+        *versionVariant = _descriptionVersion;
+        persistToFile();
+        qDebug() << "No version in config file, setting to current version" << _descriptionVersion;
+    }
+
+    {
+        // Backward compatibility migration code
+        // The config version used to be stored in a different file
+        // This moves it to the actual config file.
+        Setting::Handle<double> JSON_SETTING_VERSION("json-settings/version", 0.0);
+        if (JSON_SETTING_VERSION.isSet()) {
+            auto version = JSON_SETTING_VERSION.get();
+            *versionVariant = version;
+            persistToFile();
+            QFile::remove(settingsFilename());
+        }
+    }
 
     // What settings version were we before and what are we using now?
     // Do we need to do any re-mapping?
-    double oldVersion = JSON_SETTING_VERSION.get();
+    double oldVersion = versionVariant->toDouble();
 
     if (oldVersion != _descriptionVersion) {
         const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users";
@@ -137,12 +155,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
                 QVariant* restrictedAccess = _configMap.valueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH, true);
 
                 *restrictedAccess = QVariant(true);
-
-                // write the new settings to the json file
-                persistToFile();
-
-                // reload the master and user config so that the merged config is right
-                _configMap.loadMasterAndUserConfig(_argumentList);
             }
         }
 
@@ -172,12 +184,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
 
                     *entityServerVariant = entityServerMap;
                 }
-
-                // write the new settings to the json file
-                persistToFile();
-
-                // reload the master and user config so that the merged config is right
-                _configMap.loadMasterAndUserConfig(_argumentList);
             }
 
         }
@@ -195,12 +201,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
                 qDebug() << "Migrating plaintext password to SHA256 hash in domain-server settings.";
 
                 *passwordVariant = QCryptographicHash::hash(plaintextPassword.toUtf8(), QCryptographicHash::Sha256).toHex();
-
-                // write the new settings to file
-                persistToFile();
-
-                // reload the master and user config so the merged config is correct
-                _configMap.loadMasterAndUserConfig(_argumentList);
             }
         }
 
@@ -283,19 +283,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
             packPermissions();
         }
 
-        if (oldVersion < 1.7) {
-            // This was prior to the removal of the master config file
-            // So we write the merged config to the user config file, and stop reading from the user config file
-
-            qDebug() << "Migrating merged config to user config file. The master config file is deprecated.";
-
-            // replace the user config by the merged config
-            _configMap.getConfig() = _configMap.getMergedConfig();
-
-            // persist the new config so the user config file has the correctly merged config
-            persistToFile();
-        }
-
         if (oldVersion < 1.8) {
             unpackPermissions();
             // This was prior to addition of domain content replacement, add that to localhost permissions by default
@@ -316,16 +303,16 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
             QVariant* wizardCompletedOnce = _configMap.valueForKeyPath(WIZARD_COMPLETED_ONCE, true);
 
             *wizardCompletedOnce = QVariant(true);
-
-            // write the new settings to the json file
-            persistToFile();
         }
+
+        // write the current description version to our settings
+        *versionVariant = _descriptionVersion;
+
+        // write the new settings to the json file
+        persistToFile();
     }
 
     unpackPermissions();
-
-    // write the current description version to our settings
-    JSON_SETTING_VERSION.set(_descriptionVersion);
 }
 
 QVariantMap& DomainServerSettingsManager::getDescriptorsMap() {
@@ -1289,9 +1276,6 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
         }
     }
 
-    // re-merge the user and master configs after a settings change
-    _configMap.mergeMasterAndUserConfigs();
-
     return needRestart;
 }
 
diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml
index 5358ad1adc..37c3c2adab 100644
--- a/interface/resources/qml/hifi/AssetServer.qml
+++ b/interface/resources/qml/hifi/AssetServer.qml
@@ -16,10 +16,10 @@ import Qt.labs.settings 1.0
 
 import "../styles-uit"
 import "../controls-uit" as HifiControls
-import "../windows"
+import "../windows" as Windows
 import "../dialogs"
 
-ScrollingWindow {
+Windows.ScrollingWindow {
     id: root
     objectName: "AssetServer"
     title: "Asset Browser"
diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml
index fcfff02b72..775bae7969 100644
--- a/interface/resources/qml/hifi/NameCard.qml
+++ b/interface/resources/qml/hifi/NameCard.qml
@@ -432,7 +432,8 @@ Item {
         anchors.verticalCenter: nameCardRemoveConnectionImage.verticalCenter
         x: 240
         onClicked: {
-            AddressManager.goToUser(thisNameCard.userName);
+            console.log("Vist user button clicked."); // Remove after debugging.
+            AddressManager.goToUser(thisNameCard.userName, false);
             UserActivityLogger.palAction("go_to_user", thisNameCard.userName);
         }
     }
@@ -594,7 +595,10 @@ Item {
         // the avatar goes into fly mode rather than falling. However, that is not exposed to Javascript right now.
         // FIXME: it would be nice if this used the same teleport steps and smoothing as in the teleport.js script.
         // Note, however, that this script allows teleporting to a person in the air, while teleport.js is going to a grounded target.
+        // Position avatar 2 metres from the target in the direction that target avatar was facing.
         MyAvatar.position = Vec3.sum(avatar.position, Vec3.multiplyQbyV(avatar.orientation, {x: 0, y: 0, z: -2}));
-        MyAvatar.orientation = Quat.multiply(avatar.orientation, {y: 1});
+        
+        // Rotate avatar on Y axis to face target avatar and cancel out any inherited roll and pitch.
+        MyAvatar.orientation = Quat.cancelOutRollAndPitch(Quat.multiply(avatar.orientation, {y: 1}));
     }
 }
diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml
index 89f18e5ce2..efcf6ccfcf 100644
--- a/interface/resources/qml/hifi/Pal.qml
+++ b/interface/resources/qml/hifi/Pal.qml
@@ -827,7 +827,7 @@ Rectangle {
                         hoverEnabled: enabled
                         enabled: connectionsNameCard.selected && pal.activeTab == "connectionsTab"
                         onClicked: {
-                            AddressManager.goToUser(model.userName);
+                            AddressManager.goToUser(model.userName, false);
                             UserActivityLogger.palAction("go_to_user", model.userName);
                         }
                         onEntered: connectionsLocationData.color = hifi.colors.blueHighlight;
diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml
index 12c2ec1835..87ddce49ca 100644
--- a/interface/resources/qml/hifi/audio/Audio.qml
+++ b/interface/resources/qml/hifi/audio/Audio.qml
@@ -213,8 +213,8 @@ Rectangle {
                     anchors.right: parent.right
                     peak: model.peak;
                     anchors.verticalCenter: parent.verticalCenter
-                    visible: (bar.currentIndex === 1 && selectedHMD && isVR) ||
-                             (bar.currentIndex === 0 && selectedDesktop && !isVR) &&
+                    visible: ((bar.currentIndex === 1 && isVR) ||
+                             (bar.currentIndex === 0 && !isVR)) &&
                              Audio.devices.input.peakValuesAvailable;
                 }
             }
diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml
index 1fe0dcc58b..fd7ce0fdfd 100644
--- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml
+++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml
@@ -196,7 +196,38 @@ Item {
             anchors.bottom: parent.bottom;
             anchors.left: parent.left;
             anchors.right: parent.right;
-            anchors.rightMargin: 24;
+
+            Item {
+                visible: transactionHistoryModel.count === 0 && root.historyReceived;
+                anchors.centerIn: parent;
+                width: parent.width - 12;
+                height: parent.height;
+
+                HifiControlsUit.Separator {
+                colorScheme: 1;
+                    anchors.left: parent.left;
+                    anchors.right: parent.right;
+                    anchors.top: parent.top;
+                }
+
+                RalewayRegular {
+                    id: noActivityText;
+                text: "<b>The Wallet app is in closed Beta.</b><br><br>To request entry and <b>receive free HFC</b>, please contact " +
+                "<b>info@highfidelity.com</b> with your High Fidelity account username and the email address registered to that account.";
+                // Text size
+                size: 24;
+                // Style
+                color: hifi.colors.blueAccent;
+                anchors.left: parent.left;
+                anchors.leftMargin: 12;
+                anchors.right: parent.right;
+                anchors.rightMargin: 12;
+                anchors.verticalCenter: parent.verticalCenter;
+                height: paintedHeight;
+                wrapMode: Text.WordWrap;
+                horizontalAlignment: Text.AlignHCenter;
+                }
+            }
             
             ListView {
                 id: transactionHistory;
@@ -294,17 +325,6 @@ Item {
                     }
                 }
             }
-
-            // This should never be visible (since you immediately get 100 HFC)
-            FiraSansRegular {
-                id: emptyTransationHistory;
-                size: 24;
-                visible: !transactionHistory.visible && root.historyReceived;
-                text: "Recent Activity Unavailable";
-                anchors.fill: parent;
-                horizontalAlignment: Text.AlignHCenter;
-                verticalAlignment: Text.AlignVCenter;
-            }
         }
     }
 
@@ -350,7 +370,9 @@ Item {
         }
 
         root.pendingCount = pendingCount;
-        transactionHistoryModel.insert(0, {"transaction_type": "pendingCount"});
+        if (pendingCount > 0) {
+            transactionHistoryModel.insert(0, {"transaction_type": "pendingCount"});
+        }
     }
 
     //
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 4f051697ad..b21588958e 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -2693,7 +2693,7 @@ bool Application::importFromZIP(const QString& filePath) {
     qDebug() << "A zip file has been dropped in: " << filePath;
     QUrl empty;
     // handle Blocks download from Marketplace
-    if (filePath.contains("vr.google.com/downloads")) {
+    if (filePath.contains("poly.google.com/downloads")) {
         addAssetToWorldFromURL(filePath);
     } else {
         qApp->getFileDownloadInterface()->runUnzip(filePath, empty, true, true, false);
@@ -6235,7 +6235,7 @@ void Application::addAssetToWorldFromURL(QString url) {
     if (url.contains("filename")) {
         filename = url.section("filename=", 1, 1);  // Filename is in "?filename=" parameter at end of URL.
     }
-    if (url.contains("vr.google.com/downloads")) {
+    if (url.contains("poly.google.com/downloads")) {
         filename = url.section('/', -1);
         if (url.contains("noDownload")) {
             filename.remove(".zip?noDownload=false");
@@ -6270,7 +6270,7 @@ void Application::addAssetToWorldFromURLRequestFinished() {
     if (url.contains("filename")) {
         filename = url.section("filename=", 1, 1);  // Filename is in "?filename=" parameter at end of URL.
     }
-    if (url.contains("vr.google.com/downloads")) {
+    if (url.contains("poly.google.com/downloads")) {
         filename = url.section('/', -1);
         if (url.contains("noDownload")) {
             filename.remove(".zip?noDownload=false");
diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp
index e8b800db69..4cf8ba6d4e 100644
--- a/interface/src/avatar/AvatarManager.cpp
+++ b/interface/src/avatar/AvatarManager.cpp
@@ -179,6 +179,12 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
         const AvatarPriority& sortData = sortedAvatars.top();
         const auto& avatar = std::static_pointer_cast<Avatar>(sortData.avatar);
 
+        bool ignoring = DependencyManager::get<NodeList>()->isPersonalMutingNode(avatar->getID());
+        if (ignoring) {
+            sortedAvatars.pop();
+            continue;
+        }
+
         // for ALL avatars...
         if (_shouldRender) {
             avatar->ensureInScene(avatar, qApp->getMain3DScene());
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index 0fceba82f0..0dfcf93a65 100755
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -135,7 +135,7 @@ MyAvatar::MyAvatar(QThread* thread) :
     connect(&domainHandler, &DomainHandler::settingsReceived, this, &MyAvatar::restrictScaleFromDomainSettings);
 
     // when we leave a domain we lift whatever restrictions that domain may have placed on our scale
-    connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &MyAvatar::clearScaleRestriction);
+    connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &MyAvatar::leaveDomain);
 
     _bodySensorMatrix = deriveBodyFromHMDSensor();
 
@@ -2189,6 +2189,7 @@ void MyAvatar::clampScaleChangeToDomainLimits(float desiredScale) {
 
     setTargetScale(clampedTargetScale);
     qCDebug(interfaceapp, "Changed scale to %f", (double)_targetScale);
+    emit(scaleChanged());
 }
 
 float MyAvatar::getDomainMinScale() {
@@ -2278,6 +2279,18 @@ void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettings
     settings.endGroup();
 }
 
+void MyAvatar::leaveDomain() {
+    clearScaleRestriction();
+    saveAvatarScale();
+}
+
+void MyAvatar::saveAvatarScale() {
+    Settings settings;
+    settings.beginGroup("Avatar");
+    settings.setValue("scale", _targetScale);
+    settings.endGroup();
+}
+
 void MyAvatar::clearScaleRestriction() {
     _domainMinimumScale = MIN_AVATAR_SCALE;
     _domainMaximumScale = MAX_AVATAR_SCALE;
diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h
index 303a395a47..e4e8f8d02c 100644
--- a/interface/src/avatar/MyAvatar.h
+++ b/interface/src/avatar/MyAvatar.h
@@ -620,6 +620,10 @@ signals:
     void dominantHandChanged(const QString& hand);
     void sensorToWorldScaleChanged(float sensorToWorldScale);
     void attachmentsChanged();
+    void scaleChanged();
+
+private slots:
+    void leaveDomain();
 
 private:
 
@@ -637,6 +641,8 @@ private:
     virtual int parseDataFromBuffer(const QByteArray& buffer) override;
     virtual glm::vec3 getSkeletonPosition() const override;
 
+    void saveAvatarScale();
+
     glm::vec3 getScriptedMotorVelocity() const { return _scriptedMotorVelocity; }
     float getScriptedMotorTimescale() const { return _scriptedMotorTimescale; }
     QString getScriptedMotorFrame() const;
diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp
index 9d07ddb4ab..f29e46d843 100644
--- a/interface/src/commerce/QmlCommerce.cpp
+++ b/interface/src/commerce/QmlCommerce.cpp
@@ -33,7 +33,7 @@ QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) {
     connect(ledger.data(), &Ledger::updateCertificateStatus, this, &QmlCommerce::updateCertificateStatus);
     
     auto accountManager = DependencyManager::get<AccountManager>();
-    connect(accountManager.data(), &AccountManager::usernameChanged, [&]() {
+    connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() {
         setPassphrase("");
     });
 }
diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp
index 75b43a251b..32dd74279b 100644
--- a/interface/src/raypick/LaserPointer.cpp
+++ b/interface/src/raypick/LaserPointer.cpp
@@ -25,7 +25,6 @@ LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& rende
     _distanceScaleEnd(distanceScaleEnd),
     _rayPickUID(DependencyManager::get<RayPickScriptingInterface>()->createRayPick(rayProps))
 {
-    
 
     for (auto& state : _renderStates) {
         if (!enabled || state.first != _currentRenderState) {
@@ -119,23 +118,25 @@ void LaserPointer::updateRenderState(const RenderState& renderState, const Inter
         qApp->getOverlays().editOverlay(renderState.getStartID(), startProps);
     }
     glm::vec3 endVec;
-    if (((defaultState || !_lockEnd) && _objectLockEnd.first.isNull()) || type == IntersectionType::HUD) {
+    if (((defaultState || !_lockEnd) && _lockEndObject.id.isNull()) || type == IntersectionType::HUD) {
         endVec = pickRay.origin + pickRay.direction * distance;
     } else {
-        if (!_objectLockEnd.first.isNull()) {
+        if (!_lockEndObject.id.isNull()) {
             glm::vec3 pos;
             glm::quat rot;
             glm::vec3 dim;
             glm::vec3 registrationPoint;
-            if (_objectLockEnd.second) {
-                pos = vec3FromVariant(qApp->getOverlays().getProperty(_objectLockEnd.first, "position").value);
-                rot = quatFromVariant(qApp->getOverlays().getProperty(_objectLockEnd.first, "rotation").value);
-                dim = vec3FromVariant(qApp->getOverlays().getProperty(_objectLockEnd.first, "dimensions").value);
+            if (_lockEndObject.isOverlay) {
+                pos = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "position").value);
+                rot = quatFromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "rotation").value);
+                dim = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "dimensions").value);
                 registrationPoint = glm::vec3(0.5f);
             } else {
-                EntityItemProperties props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(_objectLockEnd.first);
-                pos = props.getPosition();
-                rot = props.getRotation();
+                EntityItemProperties props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(_lockEndObject.id);
+                glm::mat4 entityMat = createMatFromQuatAndPos(props.getRotation(), props.getPosition());
+                glm::mat4 finalPosAndRotMat = entityMat * _lockEndObject.offsetMat;
+                pos = extractTranslation(finalPosAndRotMat);
+                rot = glmExtractRotation(finalPosAndRotMat);
                 dim = props.getDimensions();
                 registrationPoint = props.getRegistrationPoint();
             }
@@ -209,7 +210,7 @@ void LaserPointer::update() {
     withReadLock([&] {
         RayPickResult prevRayPickResult = qApp->getRayPickManager().getPrevRayPickResult(_rayPickUID);
         if (_renderingEnabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() &&
-            (prevRayPickResult.type != IntersectionType::NONE || _laserLength > 0.0f || !_objectLockEnd.first.isNull())) {
+            (prevRayPickResult.type != IntersectionType::NONE || _laserLength > 0.0f || !_lockEndObject.id.isNull())) {
             float distance = _laserLength > 0.0f ? _laserLength : prevRayPickResult.distance;
             updateRenderState(_renderStates[_currentRenderState], prevRayPickResult.type, distance, prevRayPickResult.objectID, prevRayPickResult.searchRay, false);
             disableRenderState(_defaultRenderStates[_currentRenderState].second);
@@ -233,9 +234,11 @@ void LaserPointer::setLaserLength(const float laserLength) {
     });
 }
 
-void LaserPointer::setLockEndUUID(QUuid objectID, const bool isOverlay) {
+void LaserPointer::setLockEndUUID(QUuid objectID, const bool isOverlay, const glm::mat4& offsetMat) {
     withWriteLock([&] {
-        _objectLockEnd = std::pair<QUuid, bool>(objectID, isOverlay);
+        _lockEndObject.id = objectID;
+        _lockEndObject.isOverlay = isOverlay;
+        _lockEndObject.offsetMat = offsetMat;
     });
 }
 
diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h
index f2350c7199..896752a96e 100644
--- a/interface/src/raypick/LaserPointer.h
+++ b/interface/src/raypick/LaserPointer.h
@@ -21,6 +21,12 @@
 
 class RayPickResult;
 
+struct LockEndObject {
+    QUuid id { QUuid() };
+    bool isOverlay { false };
+    glm::mat4 offsetMat { glm::mat4() };
+};
+
 class RenderState {
 
 public:
@@ -74,7 +80,7 @@ public:
 
     void setPrecisionPicking(const bool precisionPicking);
     void setLaserLength(const float laserLength);
-    void setLockEndUUID(QUuid objectID, const bool isOverlay);
+    void setLockEndUUID(QUuid objectID, const bool isOverlay, const glm::mat4& offsetMat = glm::mat4());
 
     void setIgnoreItems(const QVector<QUuid>& ignoreItems) const;
     void setIncludeItems(const QVector<QUuid>& includeItems) const;
@@ -91,7 +97,7 @@ private:
     bool _centerEndY;
     bool _lockEnd;
     bool _distanceScaleEnd;
-    std::pair<QUuid, bool> _objectLockEnd { std::pair<QUuid, bool>(QUuid(), false)};
+    LockEndObject _lockEndObject;
 
     const QUuid _rayPickUID;
 
diff --git a/interface/src/raypick/LaserPointerManager.cpp b/interface/src/raypick/LaserPointerManager.cpp
index 9d58cc2587..45420d1488 100644
--- a/interface/src/raypick/LaserPointerManager.cpp
+++ b/interface/src/raypick/LaserPointerManager.cpp
@@ -113,9 +113,9 @@ void LaserPointerManager::setIncludeItems(const QUuid& uid, const QVector<QUuid>
     }
 }
 
-void LaserPointerManager::setLockEndUUID(const QUuid& uid, const QUuid& objectID, const bool isOverlay) const {
+void LaserPointerManager::setLockEndUUID(const QUuid& uid, const QUuid& objectID, const bool isOverlay, const glm::mat4& offsetMat) const {
     auto laserPointer = find(uid);
     if (laserPointer) {
-        laserPointer->setLockEndUUID(objectID, isOverlay);
+        laserPointer->setLockEndUUID(objectID, isOverlay, offsetMat);
     }
 }
diff --git a/interface/src/raypick/LaserPointerManager.h b/interface/src/raypick/LaserPointerManager.h
index e302318483..25089a291a 100644
--- a/interface/src/raypick/LaserPointerManager.h
+++ b/interface/src/raypick/LaserPointerManager.h
@@ -39,7 +39,7 @@ public:
     void setIgnoreItems(const QUuid& uid, const QVector<QUuid>& ignoreEntities) const;
     void setIncludeItems(const QUuid& uid, const QVector<QUuid>& includeEntities) const;
 
-    void setLockEndUUID(const QUuid& uid, const QUuid& objectID, const bool isOverlay) const;
+    void setLockEndUUID(const QUuid& uid, const QUuid& objectID, const bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const;
 
     void update();
 
diff --git a/interface/src/raypick/LaserPointerScriptingInterface.h b/interface/src/raypick/LaserPointerScriptingInterface.h
index 19262e6e5d..986c53e24f 100644
--- a/interface/src/raypick/LaserPointerScriptingInterface.h
+++ b/interface/src/raypick/LaserPointerScriptingInterface.h
@@ -35,7 +35,7 @@ public slots:
     Q_INVOKABLE void setIgnoreItems(const QUuid& uid, const QScriptValue& ignoreEntities) const;
     Q_INVOKABLE void setIncludeItems(const QUuid& uid, const QScriptValue& includeEntities) const;
 
-    Q_INVOKABLE void setLockEndUUID(const QUuid& uid, const QUuid& objectID, bool isOverlay) const { qApp->getLaserPointerManager().setLockEndUUID(uid, objectID, isOverlay); }
+    Q_INVOKABLE void setLockEndUUID(const QUuid& uid, const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const { qApp->getLaserPointerManager().setLockEndUUID(uid, objectID, isOverlay, offsetMat); }
 
 private:
     static RenderState buildRenderState(const QVariantMap& propMap);
diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp
index 42425932df..d157e29959 100644
--- a/interface/src/ui/overlays/Base3DOverlay.cpp
+++ b/interface/src/ui/overlays/Base3DOverlay.cpp
@@ -265,7 +265,7 @@ void Base3DOverlay::locationChanged(bool tellPhysics) {
     SpatiallyNestable::locationChanged(tellPhysics);
 
     // Force the actual update of the render transform through the notify call
-    notifyRenderTransformChange();
+    notifyRenderVariableChange();
 }
 
 void Base3DOverlay::parentDeleted() {
@@ -275,18 +275,21 @@ void Base3DOverlay::parentDeleted() {
 void Base3DOverlay::update(float duration) {
     // In Base3DOverlay, if its location or bound changed, the renderTrasnformDirty flag is true.
     // then the correct transform used for rendering is computed in the update transaction and assigned.
-    if (_renderTransformDirty) {
+    if (_renderVariableDirty) {
         auto itemID = getRenderItemID();
         if (render::Item::isValidID(itemID)) {
             // Capture the render transform value in game loop before
             auto latestTransform = evalRenderTransform();
-            _renderTransformDirty = false;
+            bool latestVisible = getVisible();
+            _renderVariableDirty = false;
             render::ScenePointer scene = qApp->getMain3DScene();
             render::Transaction transaction;
-            transaction.updateItem<Overlay>(itemID, [latestTransform](Overlay& data) {
+            transaction.updateItem<Overlay>(itemID, [latestTransform, latestVisible](Overlay& data) {
                 auto overlay3D = dynamic_cast<Base3DOverlay*>(&data);
                 if (overlay3D) {
+                    // TODO: overlays need to communicate all relavent render properties through transactions
                     overlay3D->setRenderTransform(latestTransform);
+                    overlay3D->setRenderVisible(latestVisible);
                 }
             });
             scene->enqueueTransaction(transaction);
@@ -294,8 +297,8 @@ void Base3DOverlay::update(float duration) {
     }
 }
 
-void Base3DOverlay::notifyRenderTransformChange() const {
-    _renderTransformDirty = true;
+void Base3DOverlay::notifyRenderVariableChange() const {
+    _renderVariableDirty = true;
 }
 
 Transform Base3DOverlay::evalRenderTransform() {
@@ -306,8 +309,17 @@ void Base3DOverlay::setRenderTransform(const Transform& transform) {
     _renderTransform = transform;
 }
 
+void Base3DOverlay::setRenderVisible(bool visible) {
+    _renderVisible = visible;
+}
+
 SpatialParentTree* Base3DOverlay::getParentTree() const {
     auto entityTreeRenderer = qApp->getEntities();
     EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr;
     return entityTree.get();
 }
+
+void Base3DOverlay::setVisible(bool visible) {
+    Parent::setVisible(visible);
+    notifyRenderVariableChange();
+}
\ No newline at end of file
diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h
index 436cfbf97b..83c5873260 100644
--- a/interface/src/ui/overlays/Base3DOverlay.h
+++ b/interface/src/ui/overlays/Base3DOverlay.h
@@ -18,11 +18,14 @@
 
 class Base3DOverlay : public Overlay, public SpatiallyNestable {
     Q_OBJECT
+    using Parent = Overlay;
 
 public:
     Base3DOverlay();
     Base3DOverlay(const Base3DOverlay* base3DOverlay);
 
+    void setVisible(bool visible) override;
+
     virtual OverlayID getOverlayID() const override { return OverlayID(getID().toString()); }
     void setOverlayID(OverlayID overlayID) override { setID(overlayID); }
 
@@ -56,7 +59,7 @@ public:
 
     void update(float deltatime) override;
 
-    void notifyRenderTransformChange() const;
+    void notifyRenderVariableChange() const;
 
     void setProperties(const QVariantMap& properties) override;
     QVariant getProperty(const QString& property) override;
@@ -76,8 +79,10 @@ protected:
     virtual void parentDeleted() override;
 
     mutable Transform _renderTransform;
+    mutable bool _renderVisible;
     virtual Transform evalRenderTransform();
     virtual void setRenderTransform(const Transform& transform);
+    void setRenderVisible(bool visible);
     const Transform& getRenderTransform() const { return _renderTransform; }
 
     float _lineWidth;
@@ -87,7 +92,7 @@ protected:
     bool _drawInFront;
     bool _drawHUDLayer;
     bool _isGrabbable { false };
-    mutable bool _renderTransformDirty{ true };
+    mutable bool _renderVariableDirty { true };
 
     QString _name;
 };
diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp
index 536b2c764f..69ce331c99 100644
--- a/interface/src/ui/overlays/Circle3DOverlay.cpp
+++ b/interface/src/ui/overlays/Circle3DOverlay.cpp
@@ -59,7 +59,7 @@ Circle3DOverlay::~Circle3DOverlay() {
     }
 }
 void Circle3DOverlay::render(RenderArgs* args) {
-    if (!_visible) {
+    if (!_renderVisible) {
         return; // do nothing if we're not visible
     }
 
@@ -259,7 +259,7 @@ void Circle3DOverlay::render(RenderArgs* args) {
 }
 
 const render::ShapeKey Circle3DOverlay::getShapeKey() {
-    auto builder = render::ShapeKey::Builder().withoutCullFace().withUnlit();
+    auto builder = render::ShapeKey::Builder().withoutCullFace();
     if (isTransparent()) {
         builder.withTranslucent();
     }
diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp
index b6df1dbc31..caa9a1b397 100644
--- a/interface/src/ui/overlays/Cube3DOverlay.cpp
+++ b/interface/src/ui/overlays/Cube3DOverlay.cpp
@@ -44,7 +44,7 @@ Cube3DOverlay::~Cube3DOverlay() {
 }
 
 void Cube3DOverlay::render(RenderArgs* args) {
-    if (!_visible) {
+    if (!_renderVisible) {
         return; // do nothing if we're not visible
     }
 
diff --git a/interface/src/ui/overlays/Grid3DOverlay.cpp b/interface/src/ui/overlays/Grid3DOverlay.cpp
index ca275368cb..6fd132b531 100644
--- a/interface/src/ui/overlays/Grid3DOverlay.cpp
+++ b/interface/src/ui/overlays/Grid3DOverlay.cpp
@@ -53,7 +53,7 @@ AABox Grid3DOverlay::getBounds() const {
 }
 
 void Grid3DOverlay::render(RenderArgs* args) {
-    if (!_visible) {
+    if (!_renderVisible) {
         return; // do nothing if we're not visible
     }
 
diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp
index 313d71fc40..a8ae293c83 100644
--- a/interface/src/ui/overlays/Image3DOverlay.cpp
+++ b/interface/src/ui/overlays/Image3DOverlay.cpp
@@ -60,7 +60,7 @@ void Image3DOverlay::update(float deltatime) {
 }
 
 void Image3DOverlay::render(RenderArgs* args) {
-    if (!_visible || !getParentVisible() || !_texture || !_texture->isLoaded()) {
+    if (!_renderVisible || !getParentVisible() || !_texture || !_texture->isLoaded()) {
         return;
     }
 
diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp
index bdb35d4f49..5ef820b2e0 100644
--- a/interface/src/ui/overlays/Line3DOverlay.cpp
+++ b/interface/src/ui/overlays/Line3DOverlay.cpp
@@ -96,7 +96,7 @@ void Line3DOverlay::setEnd(const glm::vec3& end) {
     } else {
         _direction = glm::vec3(0.0f);
     }
-    notifyRenderTransformChange();
+    notifyRenderVariableChange();
 }
 
 void Line3DOverlay::setLocalEnd(const glm::vec3& localEnd) {
@@ -123,7 +123,7 @@ AABox Line3DOverlay::getBounds() const {
 }
 
 void Line3DOverlay::render(RenderArgs* args) {
-    if (!_visible) {
+    if (!_renderVisible) {
         return; // do nothing if we're not visible
     }
 
diff --git a/interface/src/ui/overlays/Planar3DOverlay.cpp b/interface/src/ui/overlays/Planar3DOverlay.cpp
index ac3fe66ddc..a9f0bfe2f1 100644
--- a/interface/src/ui/overlays/Planar3DOverlay.cpp
+++ b/interface/src/ui/overlays/Planar3DOverlay.cpp
@@ -37,7 +37,7 @@ AABox Planar3DOverlay::getBounds() const {
 
 void Planar3DOverlay::setDimensions(const glm::vec2& value) {
     _dimensions = value;
-    notifyRenderTransformChange();
+    notifyRenderVariableChange();
 }
 
 void Planar3DOverlay::setProperties(const QVariantMap& properties) {
diff --git a/interface/src/ui/overlays/Rectangle3DOverlay.cpp b/interface/src/ui/overlays/Rectangle3DOverlay.cpp
index 47d47b76a5..f2e324206d 100644
--- a/interface/src/ui/overlays/Rectangle3DOverlay.cpp
+++ b/interface/src/ui/overlays/Rectangle3DOverlay.cpp
@@ -23,7 +23,6 @@ Rectangle3DOverlay::Rectangle3DOverlay() :
     for (size_t i = 0; i < _rectGeometryIds.size(); ++i) {
         _rectGeometryIds[i] = geometryCache->allocateID();
     }
-    qDebug() << "Building rect3d overlay";
 }
 
 Rectangle3DOverlay::Rectangle3DOverlay(const Rectangle3DOverlay* rectangle3DOverlay) :
@@ -34,11 +33,9 @@ Rectangle3DOverlay::Rectangle3DOverlay(const Rectangle3DOverlay* rectangle3DOver
     for (size_t i = 0; i < _rectGeometryIds.size(); ++i) {
         _rectGeometryIds[i] = geometryCache->allocateID();
     }
-    qDebug() << "Building rect3d overlay";
 }
 
 Rectangle3DOverlay::~Rectangle3DOverlay() {
-    qDebug() << "Destryoing rect3d overlay";
     auto geometryCache = DependencyManager::get<GeometryCache>();
     if (geometryCache) {
         geometryCache->releaseID(_geometryCacheID);
@@ -49,7 +46,7 @@ Rectangle3DOverlay::~Rectangle3DOverlay() {
 }
 
 void Rectangle3DOverlay::render(RenderArgs* args) {
-    if (!_visible) {
+    if (!_renderVisible) {
         return; // do nothing if we're not visible
     }
 
@@ -58,18 +55,11 @@ void Rectangle3DOverlay::render(RenderArgs* args) {
     const float MAX_COLOR = 255.0f;
     glm::vec4 rectangleColor(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha);
 
-    glm::vec3 position = getPosition();
-    glm::vec2 dimensions = getDimensions();
-    glm::vec2 halfDimensions = dimensions * 0.5f;
-    glm::quat rotation = getRotation();
-
     auto batch = args->_batch;
-
     if (batch) {
-        // FIXME Start using the _renderTransform instead of calling for Transform and Dimensions from here, do the custom things needed in evalRenderTransform()
-        Transform transform;
-        transform.setTranslation(position);
-        transform.setRotation(rotation);
+        Transform transform = getRenderTransform();
+        glm::vec2 halfDimensions = transform.getScale() * 0.5f;
+        transform.setScale(1.0f);
 
         batch->setModelTransform(transform);
         auto geometryCache = DependencyManager::get<GeometryCache>();
@@ -124,8 +114,3 @@ void Rectangle3DOverlay::setProperties(const QVariantMap& properties) {
 Rectangle3DOverlay* Rectangle3DOverlay::createClone() const {
     return new Rectangle3DOverlay(this);
 }
-
-Transform Rectangle3DOverlay::evalRenderTransform() {
-    return getTransform();
-}
-
diff --git a/interface/src/ui/overlays/Rectangle3DOverlay.h b/interface/src/ui/overlays/Rectangle3DOverlay.h
index a26ed524fc..645553ed38 100644
--- a/interface/src/ui/overlays/Rectangle3DOverlay.h
+++ b/interface/src/ui/overlays/Rectangle3DOverlay.h
@@ -29,9 +29,6 @@ public:
 
     virtual Rectangle3DOverlay* createClone() const override;
 
-protected:
-    Transform evalRenderTransform() override;
-
 private:
     int _geometryCacheID;
     std::array<int, 4> _rectGeometryIds;
diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp
index a3b51d40bf..bd8e6a9728 100644
--- a/interface/src/ui/overlays/Shape3DOverlay.cpp
+++ b/interface/src/ui/overlays/Shape3DOverlay.cpp
@@ -24,7 +24,7 @@ Shape3DOverlay::Shape3DOverlay(const Shape3DOverlay* Shape3DOverlay) :
 }
 
 void Shape3DOverlay::render(RenderArgs* args) {
-    if (!_visible) {
+    if (!_renderVisible) {
         return; // do nothing if we're not visible
     }
 
diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp
index c9fc25b252..2013e5689a 100644
--- a/interface/src/ui/overlays/Sphere3DOverlay.cpp
+++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp
@@ -27,7 +27,7 @@ Sphere3DOverlay::Sphere3DOverlay(const Sphere3DOverlay* Sphere3DOverlay) :
 }
 
 void Sphere3DOverlay::render(RenderArgs* args) {
-    if (!_visible) {
+    if (!_renderVisible) {
         return; // do nothing if we're not visible
     }
 
diff --git a/interface/src/ui/overlays/Text3DOverlay.cpp b/interface/src/ui/overlays/Text3DOverlay.cpp
index d8188cf6dc..1cbc73304b 100644
--- a/interface/src/ui/overlays/Text3DOverlay.cpp
+++ b/interface/src/ui/overlays/Text3DOverlay.cpp
@@ -92,7 +92,7 @@ void Text3DOverlay::update(float deltatime) {
 }
 
 void Text3DOverlay::render(RenderArgs* args) {
-    if (!_visible || !getParentVisible()) {
+    if (!_renderVisible || !getParentVisible()) {
         return; // do nothing if we're not visible
     }
 
diff --git a/interface/src/ui/overlays/Volume3DOverlay.cpp b/interface/src/ui/overlays/Volume3DOverlay.cpp
index 8aa8490937..e5a418cce5 100644
--- a/interface/src/ui/overlays/Volume3DOverlay.cpp
+++ b/interface/src/ui/overlays/Volume3DOverlay.cpp
@@ -28,7 +28,7 @@ AABox Volume3DOverlay::getBounds() const {
 
 void Volume3DOverlay::setDimensions(const glm::vec3& value) {
     _localBoundingBox.setBox(-value / 2.0f, value);
-    notifyRenderTransformChange();
+    notifyRenderVariableChange();
 }
 
 void Volume3DOverlay::setProperties(const QVariantMap& properties) {
diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp
index 42d0ea0f12..d418a79fbf 100644
--- a/interface/src/ui/overlays/Web3DOverlay.cpp
+++ b/interface/src/ui/overlays/Web3DOverlay.cpp
@@ -268,7 +268,7 @@ Q_INVOKABLE int Web3DOverlay::deviceIdByTouchPoint(qreal x, qreal y) {
 }
 
 void Web3DOverlay::render(RenderArgs* args) {
-    if (!_visible || !getParentVisible()) {
+    if (!_renderVisible || !getParentVisible()) {
         return;
     }
 
diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp
index 0f2bce5ca4..3dad4e3fb6 100644
--- a/libraries/animation/src/Rig.cpp
+++ b/libraries/animation/src/Rig.cpp
@@ -152,6 +152,7 @@ void Rig::overrideRoleAnimation(const QString& role, const QString& url, float f
             const float REFERENCE_FRAMES_PER_SECOND = 30.0f;
             float timeScale = fps / REFERENCE_FRAMES_PER_SECOND;
             auto clipNode = std::make_shared<AnimClip>(role, url, firstFrame, lastFrame, timeScale, loop, false);
+            _roleAnimStates[role] = { role, url, fps, loop, firstFrame, lastFrame };
             AnimNode::Pointer parent = node->getParent();
             parent->replaceChild(node, clipNode);
         } else {
@@ -302,7 +303,9 @@ void Rig::setModelOffset(const glm::mat4& modelOffsetMat) {
         _rigToGeometryTransform = glm::inverse(_geometryToRigTransform);
 
         // rebuild cached default poses
-        buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses);
+        if (_animSkeleton) {
+            buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses);
+        }
     }
 }
 
@@ -1638,6 +1641,11 @@ void Rig::initAnimGraph(const QUrl& url) {
                 _userAnimState = { UserAnimState::None, "", 30.0f, false, 0.0f, 0.0f };
                 overrideAnimation(origState.url, origState.fps, origState.loop, origState.firstFrame, origState.lastFrame);
             }
+            // restore the role animations we had before reset.
+            for (auto& roleAnimState : _roleAnimStates) {
+                auto roleState = roleAnimState.second;
+                overrideRoleAnimation(roleState.role, roleState.url, roleState.fps, roleState.loop, roleState.firstFrame, roleState.lastFrame);
+            }
             _animLoading = false;
 
             emit onLoadComplete();
diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h
index 18d49c5f1e..e9cc444bd4 100644
--- a/libraries/animation/src/Rig.h
+++ b/libraries/animation/src/Rig.h
@@ -335,8 +335,22 @@ protected:
         float firstFrame;
         float lastFrame;
     };
+    
+    struct RoleAnimState {
+       RoleAnimState() {}
+       RoleAnimState(const QString& roleId, const QString& urlIn, float fpsIn, bool loopIn, float firstFrameIn, float lastFrameIn) :
+            role(roleId), url(urlIn), fps(fpsIn), loop(loopIn), firstFrame(firstFrameIn), lastFrame(lastFrameIn) {}
+
+        QString role;
+        QString url;
+        float fps;
+        bool loop;
+        float firstFrame;
+        float lastFrame;
+    };
 
     UserAnimState _userAnimState;
+    std::map<QString, RoleAnimState> _roleAnimStates;
 
     float _leftHandOverlayAlpha { 0.0f };
     float _rightHandOverlayAlpha { 0.0f };
diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
index 142e57c9e5..d2dc116e15 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
+++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
@@ -728,19 +728,19 @@ void Avatar::simulateAttachments(float deltaTime) {
         glm::quat jointRotation;
         if (attachment.isSoft) {
             // soft attachments do not have transform offsets
-            model->setTranslation(getPosition());
-            model->setRotation(getOrientation() * Quaternions::Y_180);
+            model->setTransformNoUpdateRenderItems(Transform(getOrientation() * Quaternions::Y_180, glm::vec3(1.0), getPosition()));
             model->simulate(deltaTime);
+            model->updateRenderItems();
         } else {
             if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPosition) &&
                 _skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRotation)) {
-                model->setTranslation(jointPosition + jointRotation * attachment.translation * getModelScale());
-                model->setRotation(jointRotation * attachment.rotation);
+                model->setTransformNoUpdateRenderItems(Transform(jointRotation * attachment.rotation, glm::vec3(1.0), jointPosition + jointRotation * attachment.translation * getModelScale()));
                 float scale = getModelScale() * attachment.scale;
                 model->setScaleToFit(true, model->getNaturalDimensions() * scale, true); // hack to force rescale
                 model->setSnapModelToCenter(false); // hack to force resnap
                 model->setSnapModelToCenter(true);
                 model->simulate(deltaTime);
+                model->updateRenderItems();
             }
         }
     }
diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp
index 877bcd9353..bd313ac133 100644
--- a/libraries/avatars/src/AvatarData.cpp
+++ b/libraries/avatars/src/AvatarData.cpp
@@ -2223,11 +2223,13 @@ glm::vec3 variantToVec3(const QVariant& var) {
     return result;
 }
 
-void AttachmentData::fromVariant(const QVariant& variant) {
+bool AttachmentData::fromVariant(const QVariant& variant) {
+    bool isValid = false;
     auto map = variant.toMap();
     if (map.contains("modelUrl")) {
         auto urlString = map["modelUrl"].toString();
         modelURL = urlString;
+        isValid = true;
     }
     if (map.contains("jointName")) {
         jointName = map["jointName"].toString();
@@ -2244,6 +2246,7 @@ void AttachmentData::fromVariant(const QVariant& variant) {
     if (map.contains("soft")) {
         isSoft = map["soft"].toBool();
     }
+    return isValid;
 }
 
 QVariantList AvatarData::getAttachmentsVariant() const {
@@ -2259,8 +2262,7 @@ void AvatarData::setAttachmentsVariant(const QVariantList& variant) {
     newAttachments.reserve(variant.size());
     for (const auto& attachmentVar : variant) {
         AttachmentData attachment;
-        attachment.fromVariant(attachmentVar);
-        if (!attachment.modelURL.isEmpty()) {
+        if (attachment.fromVariant(attachmentVar)) {
             newAttachments.append(attachment);
         }
     }
diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h
index 89fe270af1..fcfeaf7741 100644
--- a/libraries/avatars/src/AvatarData.h
+++ b/libraries/avatars/src/AvatarData.h
@@ -903,7 +903,7 @@ public:
     void fromJson(const QJsonObject& json);
 
     QVariant toVariant() const;
-    void fromVariant(const QVariant& variant);
+    bool fromVariant(const QVariant& variant);
 };
 
 QDataStream& operator<<(QDataStream& out, const AttachmentData& attachment);
diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp
index f9e88b430f..9e4d832037 100644
--- a/libraries/entities-renderer/src/RenderableEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp
@@ -295,6 +295,14 @@ void EntityRenderer::updateInScene(const ScenePointer& scene, Transaction& trans
     });
 }
 
+void EntityRenderer::clearSubRenderItemIDs() {
+    _subRenderItemIDs.clear();
+}
+
+void EntityRenderer::setSubRenderItemIDs(const render::ItemIDs& ids) {
+    _subRenderItemIDs = ids;
+}
+
 //
 // Internal methods
 //
diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h
index d770e7c7aa..ed636ebf73 100644
--- a/libraries/entities-renderer/src/RenderableEntityItem.h
+++ b/libraries/entities-renderer/src/RenderableEntityItem.h
@@ -49,6 +49,9 @@ public:
     virtual bool addToScene(const ScenePointer& scene, Transaction& transaction) final;
     virtual void removeFromScene(const ScenePointer& scene, Transaction& transaction);
 
+    void clearSubRenderItemIDs();
+    void setSubRenderItemIDs(const render::ItemIDs& ids);
+
 protected:
     virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); }
     virtual void onAddToScene(const EntityItemPointer& entity);
@@ -113,6 +116,7 @@ protected:
     SharedSoundPointer _collisionSound;
     QUuid _changeHandlerId;
     ItemID _renderItemID{ Item::INVALID_ITEM_ID };
+    ItemIDs _subRenderItemIDs;
     quint64 _fadeStartTime{ usecTimestampNow() };
     bool _isFading{ _entitiesShouldFadeFunction() };
     bool _prevIsTransparent { false };
diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
index 03380ad321..6b30d2a7e8 100644
--- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
@@ -953,7 +953,7 @@ ItemKey ModelEntityRenderer::getKey() {
 
 uint32_t ModelEntityRenderer::metaFetchMetaSubItems(ItemIDs& subItems) { 
     if (_model) {
-        auto metaSubItems = _model->fetchRenderItemIDs();
+        auto metaSubItems = _subRenderItemIDs;
         subItems.insert(subItems.end(), metaSubItems.begin(), metaSubItems.end());
         return (uint32_t)metaSubItems.size();
     }
@@ -1202,6 +1202,10 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
         if ((bool)model) {
             model->removeFromScene(scene, transaction);
             withWriteLock([&] { _model.reset(); });
+            transaction.updateItem<PayloadProxyInterface>(getRenderItemID(), [](PayloadProxyInterface& data) {
+                auto entityRenderer = static_cast<EntityRenderer*>(&data);
+                entityRenderer->clearSubRenderItemIDs();
+            });
         }
         return;
     }
@@ -1297,6 +1301,12 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
             render::Item::Status::Getters statusGetters;
             makeStatusGetters(entity, statusGetters);
             model->addToScene(scene, transaction, statusGetters);
+
+            auto newRenderItemIDs{ model->fetchRenderItemIDs() };
+            transaction.updateItem<PayloadProxyInterface>(getRenderItemID(), [newRenderItemIDs](PayloadProxyInterface& data) {
+                auto entityRenderer = static_cast<EntityRenderer*>(&data);
+                entityRenderer->setSubRenderItemIDs(newRenderItemIDs);
+            });
         }
     }
 
diff --git a/libraries/entities/src/DiffTraversal.cpp b/libraries/entities/src/DiffTraversal.cpp
index 2f9423daa3..764c420197 100644
--- a/libraries/entities/src/DiffTraversal.cpp
+++ b/libraries/entities/src/DiffTraversal.cpp
@@ -142,7 +142,8 @@ DiffTraversal::DiffTraversal() {
     _path.reserve(MIN_PATH_DEPTH);
 }
 
-DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum) {
+DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, 
+        int32_t lodLevelOffset, bool usesViewFrustum) {
     assert(root);
     // there are three types of traversal:
     //
diff --git a/libraries/entities/src/DiffTraversal.h b/libraries/entities/src/DiffTraversal.h
index c26e48ae5f..eb7168356e 100644
--- a/libraries/entities/src/DiffTraversal.h
+++ b/libraries/entities/src/DiffTraversal.h
@@ -57,7 +57,8 @@ public:
 
     DiffTraversal();
 
-    Type prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum);
+    Type prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, 
+        bool usesViewFrustum);
 
     const ViewFrustum& getCurrentView() const { return _currentView.viewFrustum; }
     const ViewFrustum& getCompletedView() const { return _completedView.viewFrustum; }
diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp
index e82ed82093..168b0cd446 100644
--- a/libraries/entities/src/EntityEditPacketSender.cpp
+++ b/libraries/entities/src/EntityEditPacketSender.cpp
@@ -93,27 +93,41 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
 
     QByteArray bufferOut(NLPacket::maxPayloadSize(type), 0);
 
-    bool success;
+    OctreeElement::AppendState encodeResult = OctreeElement::PARTIAL; // start the loop assuming there's more to send
     auto nodeList = DependencyManager::get<NodeList>();
+
+    EntityPropertyFlags didntFitProperties;
+    EntityItemProperties propertiesCopy = properties;
+
     if (properties.parentIDChanged() && properties.getParentID() == AVATAR_SELF_ID) {
-        EntityItemProperties propertiesCopy = properties;
         const QUuid myNodeID = nodeList->getSessionUUID();
         propertiesCopy.setParentID(myNodeID);
-        success = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, propertiesCopy, bufferOut);
-    } else {
-        success = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, properties, bufferOut);
     }
 
-    if (success) {
-        #ifdef WANT_DEBUG
-            qCDebug(entities) << "calling queueOctreeEditMessage()...";
-            qCDebug(entities) << "    id:" << entityItemID;
-            qCDebug(entities) << "    properties:" << properties;
-        #endif
-        queueOctreeEditMessage(type, bufferOut);
-        if (type == PacketType::EntityAdd && !properties.getCertificateID().isEmpty()) {
-            emit addingEntityWithCertificate(properties.getCertificateID(), DependencyManager::get<AddressManager>()->getPlaceName());
+    EntityPropertyFlags requestedProperties = propertiesCopy.getChangedProperties();
+
+    while (encodeResult == OctreeElement::PARTIAL) {
+        encodeResult = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, propertiesCopy, bufferOut, requestedProperties, didntFitProperties);
+
+        if (encodeResult != OctreeElement::NONE) {
+            #ifdef WANT_DEBUG
+                qCDebug(entities) << "calling queueOctreeEditMessage()...";
+                qCDebug(entities) << "    id:" << entityItemID;
+                qCDebug(entities) << "    properties:" << properties;
+            #endif
+            queueOctreeEditMessage(type, bufferOut);
+            if (type == PacketType::EntityAdd && !properties.getCertificateID().isEmpty()) {
+                emit addingEntityWithCertificate(properties.getCertificateID(), DependencyManager::get<AddressManager>()->getPlaceName());
+            }
         }
+
+        // if we still have properties to send, switch the message type to edit, and request only the packets that didn't fit
+        if (encodeResult != OctreeElement::COMPLETED) {
+            type = PacketType::EntityEdit;
+            requestedProperties = didntFitProperties;
+        }
+
+        bufferOut.resize(NLPacket::maxPayloadSize(type)); // resize our output buffer for the next packet
     }
 }
 
diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp
index f6f4e48a73..3f054e1ccb 100644
--- a/libraries/entities/src/EntityItem.cpp
+++ b/libraries/entities/src/EntityItem.cpp
@@ -83,7 +83,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
     requestedProperties += PROP_ANGULAR_VELOCITY;
     requestedProperties += PROP_ACCELERATION;
 
-    requestedProperties += PROP_DIMENSIONS; // NOTE: PROP_RADIUS obsolete
+    requestedProperties += PROP_DIMENSIONS;
     requestedProperties += PROP_DENSITY;
     requestedProperties += PROP_GRAVITY;
     requestedProperties += PROP_DAMPING;
@@ -241,7 +241,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
         APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, getLocalAngularVelocity());
         APPEND_ENTITY_PROPERTY(PROP_ACCELERATION, getAcceleration());
 
-        APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getDimensions()); // NOTE: PROP_RADIUS obsolete
+        APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getDimensions());
         APPEND_ENTITY_PROPERTY(PROP_DENSITY, getDensity());
         APPEND_ENTITY_PROPERTY(PROP_GRAVITY, getGravity());
         APPEND_ENTITY_PROPERTY(PROP_DAMPING, getDamping());
diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp
index f774b208c4..108fc14e30 100644
--- a/libraries/entities/src/EntityItemProperties.cpp
+++ b/libraries/entities/src/EntityItemProperties.cpp
@@ -1221,8 +1221,9 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
 //
 // TODO: Implement support for script and visible properties.
 //
-bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties,
-                                                  QByteArray& buffer) {
+OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties,
+                QByteArray& buffer, EntityPropertyFlags requestedProperties, EntityPropertyFlags& didntFitProperties) {
+
     OctreePacketData ourDataPacket(false, buffer.size()); // create a packetData object to add out packet details too.
     OctreePacketData* packetData = &ourDataPacket; // we want a pointer to this so we can use our APPEND_ENTITY_PROPERTY macro
 
@@ -1264,17 +1265,8 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
         QByteArray encodedUpdateDelta = updateDeltaCoder;
 
         EntityPropertyFlags propertyFlags(PROP_LAST_ITEM);
-        EntityPropertyFlags requestedProperties = properties.getChangedProperties();
         EntityPropertyFlags propertiesDidntFit = requestedProperties;
 
-        // TODO: we need to handle the multi-pass form of this, similar to how we handle entity data
-        //
-        // If we are being called for a subsequent pass at appendEntityData() that failed to completely encode this item,
-        // then our modelTreeElementExtraEncodeData should include data about which properties we need to append.
-        //if (modelTreeElementExtraEncodeData && modelTreeElementExtraEncodeData->includedItems.contains(getEntityItemID())) {
-        //    requestedProperties = modelTreeElementExtraEncodeData->includedItems.value(getEntityItemID());
-        //}
-
         LevelDetails entityLevel = packetData->startLevel();
 
         // Last Edited quint64 always first, before any other details, which allows us easy access to adjusting this
@@ -1302,7 +1294,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
         int propertyCount = 0;
 
         bool headerFits = successIDFits && successTypeFits && successLastEditedFits
-        && successLastUpdatedFits && successPropertyFlagsFits;
+                && successLastUpdatedFits && successPropertyFlagsFits;
 
         int startOfEntityItemData = packetData->getUncompressedByteOffset();
 
@@ -1316,7 +1308,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
 
             APPEND_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, properties._simulationOwner.toByteArray());
             APPEND_ENTITY_PROPERTY(PROP_POSITION, properties.getPosition());
-            APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, properties.getDimensions()); // NOTE: PROP_RADIUS obsolete
+            APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, properties.getDimensions());
             APPEND_ENTITY_PROPERTY(PROP_ROTATION, properties.getRotation());
             APPEND_ENTITY_PROPERTY(PROP_DENSITY, properties.getDensity());
             APPEND_ENTITY_PROPERTY(PROP_VELOCITY, properties.getVelocity());
@@ -1472,6 +1464,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
                 properties.getType() == EntityTypes::Sphere) {
                 APPEND_ENTITY_PROPERTY(PROP_SHAPE, properties.getShape());
             }
+
             APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName());
             APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL());
             APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, properties.getActionData());
@@ -1522,12 +1515,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
 
         // If any part of the model items didn't fit, then the element is considered partial
         if (appendState != OctreeElement::COMPLETED) {
-            // TODO: handle mechanism for handling partial fitting data!
-            // add this item into our list for the next appendElementData() pass
-            //modelTreeElementExtraEncodeData->includedItems.insert(getEntityItemID(), propertiesDidntFit);
-
-            // for now, if it's not complete, it's not successful
-            success = false;
+            didntFitProperties = propertiesDidntFit;
         }
     }
 
@@ -1543,11 +1531,15 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
         } else {
             qCDebug(entities) << "ERROR - encoded edit message doesn't fit in output buffer.";
             success = false;
+            appendState = OctreeElement::NONE; // if we got here, then we didn't include the item
+            // maybe we should assert!!!
         }
     } else {
         packetData->discardSubTree();
     }
-    return success;
+
+
+    return appendState;
 }
 
 QByteArray EntityItemProperties::getPackedNormals() const {
@@ -1673,7 +1665,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
 
     READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SIMULATION_OWNER, QByteArray, setSimulationOwner);
     READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POSITION, glm::vec3, setPosition);
-    READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DIMENSIONS, glm::vec3, setDimensions);  // NOTE: PROP_RADIUS obsolete
+    READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DIMENSIONS, glm::vec3, setDimensions);
     READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ROTATION, glm::quat, setRotation);
     READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DENSITY, float, setDensity);
     READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VELOCITY, glm::vec3, setVelocity);
diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h
index a8bb063934..732dbdf69f 100644
--- a/libraries/entities/src/EntityItemProperties.h
+++ b/libraries/entities/src/EntityItemProperties.h
@@ -262,8 +262,8 @@ public:
     float getLocalRenderAlpha() const { return _localRenderAlpha; }
     void setLocalRenderAlpha(float value) { _localRenderAlpha = value; _localRenderAlphaChanged = true; }
 
-    static bool encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties,
-                                       QByteArray& buffer);
+    static OctreeElement::AppendState encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties,
+                                       QByteArray& buffer, EntityPropertyFlags requestedProperties, EntityPropertyFlags& didntFitProperties);
 
     static bool encodeEraseEntityMessage(const EntityItemID& entityItemID, QByteArray& buffer);
 
diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h
index f0f22b0091..35d40b669a 100644
--- a/libraries/entities/src/EntityPropertyFlags.h
+++ b/libraries/entities/src/EntityPropertyFlags.h
@@ -21,8 +21,7 @@ enum EntityPropertyList {
     // these properties are supported by the EntityItem base class
     PROP_VISIBLE,
     PROP_POSITION,
-    PROP_RADIUS, // NOTE: PROP_RADIUS is obsolete and only included in old format streams
-    PROP_DIMENSIONS = PROP_RADIUS,
+    PROP_DIMENSIONS,
     PROP_ROTATION,
     PROP_DENSITY,
     PROP_VELOCITY,
@@ -47,13 +46,13 @@ enum EntityPropertyList {
     PROP_ANGULAR_VELOCITY,
     PROP_ANGULAR_DAMPING,
     PROP_COLLISIONLESS,
-    PROP_DYNAMIC,
+    PROP_DYNAMIC, // 24
 
     // property used by Light entity
     PROP_IS_SPOTLIGHT,
     PROP_DIFFUSE_COLOR,
-    PROP_AMBIENT_COLOR_UNUSED,
-    PROP_SPECULAR_COLOR_UNUSED,
+    PROP_AMBIENT_COLOR_UNUSED, // FIXME - No longer used, can remove and bump protocol
+    PROP_SPECULAR_COLOR_UNUSED, // FIXME - No longer used, can remove and bump protocol
     PROP_INTENSITY, // Previously PROP_CONSTANT_ATTENUATION
     PROP_LINEAR_ATTENUATION_UNUSED,
     PROP_QUADRATIC_ATTENUATION_UNUSED,
@@ -61,30 +60,30 @@ enum EntityPropertyList {
     PROP_CUTOFF,
 
     // available to all entities
-    PROP_LOCKED,
+    PROP_LOCKED,  // 34
 
     PROP_TEXTURES,  // used by Model entities
-    PROP_ANIMATION_SETTINGS,  // used by Model entities
-    PROP_USER_DATA,  // all entities
+    PROP_ANIMATION_SETTINGS_UNUSED,  // FIXME - No longer used, can remove and bump protocol
+    PROP_USER_DATA,  // all entities -- 37
     PROP_SHAPE_TYPE, // used by Model + zones entities
 
     // used by ParticleEffect entities
-    PROP_MAX_PARTICLES,
-    PROP_LIFESPAN,
+    PROP_MAX_PARTICLES, // 39
+    PROP_LIFESPAN, // 40 -- used by all entities
     PROP_EMIT_RATE,
     PROP_EMIT_SPEED,
     PROP_EMIT_STRENGTH,
-    PROP_EMIT_ACCELERATION,
-    PROP_PARTICLE_RADIUS,
+    PROP_EMIT_ACCELERATION, // FIXME - doesn't seem to get set in mark all changed????
+    PROP_PARTICLE_RADIUS,  // 45!!
 
     PROP_COMPOUND_SHAPE_URL, // used by Model + zones entities
     PROP_MARKETPLACE_ID, // all entities
     PROP_ACCELERATION, // all entities
     PROP_SIMULATION_OWNER, // formerly known as PROP_SIMULATOR_ID
-    PROP_NAME, // all entities
+    PROP_NAME, // all entities -- 50
     PROP_COLLISION_SOUND_URL,
     PROP_RESTITUTION,
-    PROP_FRICTION,
+    PROP_FRICTION, // 53
 
     PROP_VOXEL_VOLUME_SIZE,
     PROP_VOXEL_DATA,
@@ -96,7 +95,7 @@ enum EntityPropertyList {
 
     // used by hyperlinks
     PROP_HREF,
-    PROP_DESCRIPTION,
+    PROP_DESCRIPTION, // 61
 
     PROP_FACE_CAMERA,
     PROP_SCRIPT_TIMESTAMP,
diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp
index 1a51440e91..b884dcba17 100644
--- a/libraries/networking/src/AddressManager.cpp
+++ b/libraries/networking/src/AddressManager.cpp
@@ -667,8 +667,11 @@ bool AddressManager::handleViewpoint(const QString& viewpointString, bool should
                     qCDebug(networking) << "Orientation parsed from lookup string is invalid. Will not use for location change.";
                 }
             }
-
-            emit locationChangeRequired(newPosition, orientationChanged, newOrientation, shouldFace);
+            
+            emit locationChangeRequired(newPosition, orientationChanged, 
+                LookupTrigger::VisitUserFromPAL ? cancelOutRollAndPitch(newOrientation): newOrientation,
+                shouldFace
+            );
 
         } else {
             qCDebug(networking) << "Could not jump to position from lookup string because it has an invalid value.";
@@ -732,13 +735,14 @@ bool AddressManager::setDomainInfo(const QString& hostname, quint16 port, Lookup
     return hostChanged;
 }
 
-void AddressManager::goToUser(const QString& username) {
+void AddressManager::goToUser(const QString& username, bool shouldMatchOrientation) {
     QString formattedUsername = QUrl::toPercentEncoding(username);
 
-    // for history storage handling we remember how this lookup was trigged - for a username it's always user input
+    // for history storage handling we remember how this lookup was triggered - for a username it's always user input
     QVariantMap requestParams;
-    requestParams.insert(LOOKUP_TRIGGER_KEY, static_cast<int>(LookupTrigger::UserInput));
-
+    requestParams.insert(LOOKUP_TRIGGER_KEY, static_cast<int>(
+        shouldMatchOrientation ? LookupTrigger::UserInput : LookupTrigger::VisitUserFromPAL
+    ));
     // this is a username - pull the captured name and lookup that user's location
     DependencyManager::get<AccountManager>()->sendRequest(GET_USER_LOCATION.arg(formattedUsername),
                                               AccountManagerAuth::Optional,
@@ -840,8 +844,8 @@ void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) {
             // and do not but it into the back stack
             _forwardStack.push(currentAddress());
         } else {
-            if (trigger == LookupTrigger::UserInput) {
-                // anyime the user has manually looked up an address we know we should clear the forward stack
+            if (trigger == LookupTrigger::UserInput || trigger == LookupTrigger::VisitUserFromPAL) {
+                // anyime the user has actively triggered an address we know we should clear the forward stack
                 _forwardStack.clear();
 
                 emit goForwardPossible(false);
diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h
index 366fc5dfab..2f3d896509 100644
--- a/libraries/networking/src/AddressManager.h
+++ b/libraries/networking/src/AddressManager.h
@@ -54,7 +54,8 @@ public:
         DomainPathResponse,
         Internal,
         AttemptedRefresh,
-        Suggestions
+        Suggestions,
+        VisitUserFromPAL
     };
 
     bool isConnected();
@@ -93,7 +94,7 @@ public slots:
     void goToLocalSandbox(QString path = "", LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger); }
     void goToEntry(LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(DEFAULT_HIFI_ADDRESS, trigger); }
 
-    void goToUser(const QString& username);
+    void goToUser(const QString& username, bool shouldMatchOrientation = true);
 
     void refreshPreviousLookup();
 
diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp
index 493dfdcff5..b5b4a161ef 100644
--- a/libraries/octree/src/OctreePacketData.cpp
+++ b/libraries/octree/src/OctreePacketData.cpp
@@ -68,8 +68,8 @@ bool OctreePacketData::append(const unsigned char* data, int length) {
         _dirty = true;
     } 
     
-    const bool wantDebug = false;
-    if (wantDebug && !success) {
+    #ifdef WANT_DEBUG
+    if (!success) {
         qCDebug(octree) << "OctreePacketData::append(const unsigned char* data, int length) FAILING....";
         qCDebug(octree) << "    length=" << length;
         qCDebug(octree) << "    _bytesAvailable=" << _bytesAvailable;
@@ -77,6 +77,7 @@ bool OctreePacketData::append(const unsigned char* data, int length) {
         qCDebug(octree) << "    _targetSize=" << _targetSize;
         qCDebug(octree) << "    _bytesReserved=" << _bytesReserved;
     }
+    #endif
     return success;
 }
 
@@ -647,6 +648,13 @@ void OctreePacketData::debugContent() {
     printf("\n");
 }
 
+void OctreePacketData::debugBytes() {
+    qCDebug(octree) << "    _bytesAvailable=" << _bytesAvailable;
+    qCDebug(octree) << "    _bytesInUse=" << _bytesInUse;
+    qCDebug(octree) << "    _targetSize=" << _targetSize;
+    qCDebug(octree) << "    _bytesReserved=" << _bytesReserved;
+}
+
 int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QString& result) { 
     uint16_t length;
     memcpy(&length, dataBytes, sizeof(length));
diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h
index ed6a49941b..37c171504b 100644
--- a/libraries/octree/src/OctreePacketData.h
+++ b/libraries/octree/src/OctreePacketData.h
@@ -240,6 +240,7 @@ public:
 
     /// displays contents for debugging
     void debugContent();
+    void debugBytes();
     
     static quint64 getCompressContentTime() { return _compressContentTime; } /// total time spent compressing content
     static quint64 getCompressContentCalls() { return _compressContentCalls; } /// total calls to compress content
diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp
index 07628904f1..3bb2aa2ef9 100644
--- a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp
+++ b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp
@@ -20,11 +20,22 @@ using namespace render;
 CauterizedMeshPartPayload::CauterizedMeshPartPayload(ModelPointer model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform)
     : ModelMeshPartPayload(model, meshIndex, partIndex, shapeIndex, transform, offsetTransform) {}
 
-void CauterizedMeshPartPayload::updateTransformForCauterizedMesh(
-        const Transform& renderTransform,
-        const gpu::BufferPointer& buffer) {
+void CauterizedMeshPartPayload::updateClusterBuffer(const std::vector<glm::mat4>& clusterMatrices, const std::vector<glm::mat4>& cauterizedClusterMatrices) {
+    ModelMeshPartPayload::updateClusterBuffer(clusterMatrices);
+
+    if (cauterizedClusterMatrices.size() > 1) {
+        if (!_cauterizedClusterBuffer) {
+            _cauterizedClusterBuffer = std::make_shared<gpu::Buffer>(cauterizedClusterMatrices.size() * sizeof(glm::mat4),
+                (const gpu::Byte*) cauterizedClusterMatrices.data());
+        } else {
+            _cauterizedClusterBuffer->setSubData(0, cauterizedClusterMatrices.size() * sizeof(glm::mat4),
+                (const gpu::Byte*) cauterizedClusterMatrices.data());
+        }
+    }
+}
+
+void CauterizedMeshPartPayload::updateTransformForCauterizedMesh(const Transform& renderTransform) {
     _cauterizedTransform = renderTransform;
-    _cauterizedClusterBuffer = buffer;
 }
 
 void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const {
diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.h b/libraries/render-utils/src/CauterizedMeshPartPayload.h
index 5e3135ea84..1c98f5abf3 100644
--- a/libraries/render-utils/src/CauterizedMeshPartPayload.h
+++ b/libraries/render-utils/src/CauterizedMeshPartPayload.h
@@ -15,7 +15,9 @@ class CauterizedMeshPartPayload : public ModelMeshPartPayload {
 public:
     CauterizedMeshPartPayload(ModelPointer model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform);
 
-    void updateTransformForCauterizedMesh(const Transform& renderTransform, const gpu::BufferPointer& buffer);
+    void updateClusterBuffer(const std::vector<glm::mat4>& clusterMatrices, const std::vector<glm::mat4>& cauterizedClusterMatrices);
+
+    void updateTransformForCauterizedMesh(const Transform& renderTransform);
 
     void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const override;
 
diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp
index 47ada457a0..30121a232d 100644
--- a/libraries/render-utils/src/CauterizedModel.cpp
+++ b/libraries/render-utils/src/CauterizedModel.cpp
@@ -48,7 +48,7 @@ void CauterizedModel::createVisibleRenderItemSet() {
         const auto& meshes = _renderGeometry->getMeshes();
 
         // all of our mesh vectors must match in size
-        if ((int)meshes.size() != _meshStates.size()) {
+        if (meshes.size() != _meshStates.size()) {
             qCDebug(renderutils) << "WARNING!!!! Mesh Sizes don't match! We will not segregate mesh groups yet.";
             return;
         }
@@ -57,6 +57,7 @@ void CauterizedModel::createVisibleRenderItemSet() {
         Q_ASSERT(_modelMeshRenderItems.isEmpty());
 
         _modelMeshRenderItems.clear();
+        _modelMeshRenderItemShapes.clear();
 
         Transform transform;
         transform.setTranslation(_translation);
@@ -80,6 +81,7 @@ void CauterizedModel::createVisibleRenderItemSet() {
             for (int partIndex = 0; partIndex < numParts; partIndex++) {
                 auto ptr = std::make_shared<CauterizedMeshPartPayload>(shared_from_this(), i, partIndex, shapeID, transform, offset);
                 _modelMeshRenderItems << std::static_pointer_cast<ModelMeshPartPayload>(ptr);
+                _modelMeshRenderItemShapes.emplace_back(ShapeInfo{ (int)i });
                 shapeID++;
             }
         }
@@ -102,7 +104,7 @@ void CauterizedModel::updateClusterMatrices() {
     _needsUpdateClusterMatrices = false;
     const FBXGeometry& geometry = getFBXGeometry();
 
-    for (int i = 0; i < _meshStates.size(); i++) {
+    for (int i = 0; i < (int)_meshStates.size(); i++) {
         Model::MeshState& state = _meshStates[i];
         const FBXMesh& mesh = geometry.meshes.at(i);
         for (int j = 0; j < mesh.clusters.size(); j++) {
@@ -110,17 +112,6 @@ void CauterizedModel::updateClusterMatrices() {
             auto jointMatrix = _rig.getJointTransform(cluster.jointIndex);
             glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
         }
-
-        // Once computed the cluster matrices, update the buffer(s)
-        if (mesh.clusters.size() > 1) {
-            if (!state.clusterBuffer) {
-                state.clusterBuffer = std::make_shared<gpu::Buffer>(state.clusterMatrices.size() * sizeof(glm::mat4),
-                                                                    (const gpu::Byte*) state.clusterMatrices.constData());
-            } else {
-                state.clusterBuffer->setSubData(0, state.clusterMatrices.size() * sizeof(glm::mat4),
-                                                (const gpu::Byte*) state.clusterMatrices.constData());
-            }
-        }
     }
 
     // as an optimization, don't build cautrizedClusterMatrices if the boneSet is empty.
@@ -143,17 +134,6 @@ void CauterizedModel::updateClusterMatrices() {
                 }
                 glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
             }
-
-            if (!_cauterizeBoneSet.empty() && (state.clusterMatrices.size() > 1)) {
-                if (!state.clusterBuffer) {
-                    state.clusterBuffer =
-                        std::make_shared<gpu::Buffer>(state.clusterMatrices.size() * sizeof(glm::mat4),
-                                                      (const gpu::Byte*) state.clusterMatrices.constData());
-                } else {
-                    state.clusterBuffer->setSubData(0, state.clusterMatrices.size() * sizeof(glm::mat4),
-                                                              (const gpu::Byte*) state.clusterMatrices.constData());
-                }
-            }
         }
     }
 
@@ -181,11 +161,11 @@ void CauterizedModel::updateRenderItems() {
         // queue up this work for later processing, at the end of update and just before rendering.
         // the application will ensure only the last lambda is actually invoked.
         void* key = (void*)this;
-        std::weak_ptr<Model> weakSelf = shared_from_this();
-        AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [weakSelf, scale]() {
+        std::weak_ptr<CauterizedModel> weakSelf = std::dynamic_pointer_cast<CauterizedModel>(shared_from_this());
+        AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [weakSelf]() {
             // do nothing, if the model has already been destroyed.
             auto self = weakSelf.lock();
-            if (!self) {
+            if (!self || !self->isLoaded()) {
                 return;
             }
 
@@ -198,37 +178,28 @@ void CauterizedModel::updateRenderItems() {
             modelTransform.setTranslation(self->getTranslation());
             modelTransform.setRotation(self->getRotation());
 
-            Transform scaledModelTransform(modelTransform);
-            scaledModelTransform.setScale(scale);
-
-            uint32_t deleteGeometryCounter = self->getGeometryCounter();
-
             render::Transaction transaction;
-            QList<render::ItemID> keys = self->getRenderItems().keys();
-            foreach (auto itemID, keys) {
-                transaction.updateItem<CauterizedMeshPartPayload>(itemID, [modelTransform, deleteGeometryCounter](CauterizedMeshPartPayload& data) {
-                    ModelPointer model = data._model.lock();
-                    if (model && model->isLoaded()) {
-                        // Ensure the model geometry was not reset between frames
-                        if (deleteGeometryCounter == model->getGeometryCounter()) {
-                            // this stuff identical to what happens in regular Model
-                            const Model::MeshState& state = model->getMeshState(data._meshIndex);
-                            Transform renderTransform = modelTransform;
-                            if (state.clusterMatrices.size() == 1) {
-                                renderTransform = modelTransform.worldTransform(Transform(state.clusterMatrices[0]));
-                            }
-                            data.updateTransformForSkinnedMesh(renderTransform, modelTransform, state.clusterBuffer);
+            for (int i = 0; i < (int)self->_modelMeshRenderItemIDs.size(); i++) {
 
-                            // this stuff for cauterized mesh
-                            CauterizedModel* cModel = static_cast<CauterizedModel*>(model.get());
-                            const Model::MeshState& cState = cModel->getCauterizeMeshState(data._meshIndex);
-                            renderTransform = modelTransform;
-                            if (cState.clusterMatrices.size() == 1) {
-                                renderTransform = modelTransform.worldTransform(Transform(cState.clusterMatrices[0]));
-                            }
-                            data.updateTransformForCauterizedMesh(renderTransform, cState.clusterBuffer);
-                        }
+                auto itemID = self->_modelMeshRenderItemIDs[i];
+                auto meshIndex = self->_modelMeshRenderItemShapes[i].meshIndex;
+                auto clusterMatrices(self->getMeshState(meshIndex).clusterMatrices);
+                auto clusterMatricesCauterized(self->getCauterizeMeshState(meshIndex).clusterMatrices);
+
+                transaction.updateItem<CauterizedMeshPartPayload>(itemID, [modelTransform, clusterMatrices, clusterMatricesCauterized](CauterizedMeshPartPayload& data) {
+                    data.updateClusterBuffer(clusterMatrices, clusterMatricesCauterized);
+
+                    Transform renderTransform = modelTransform;
+                    if (clusterMatrices.size() == 1) {
+                        renderTransform = modelTransform.worldTransform(Transform(clusterMatrices[0]));
                     }
+                    data.updateTransformForSkinnedMesh(renderTransform, modelTransform);
+
+                    renderTransform = modelTransform;
+                    if (clusterMatricesCauterized.size() == 1) {
+                        renderTransform = modelTransform.worldTransform(Transform(clusterMatricesCauterized[0]));
+                    }
+                    data.updateTransformForCauterizedMesh(renderTransform);
                 });
             }
 
diff --git a/libraries/render-utils/src/DrawHaze.cpp b/libraries/render-utils/src/DrawHaze.cpp
index 5faa1b206b..4431c1bbc3 100644
--- a/libraries/render-utils/src/DrawHaze.cpp
+++ b/libraries/render-utils/src/DrawHaze.cpp
@@ -140,7 +140,7 @@ void DrawHaze::run(const render::RenderContextPointer& renderContext, const Inpu
         gpu::StatePointer state = gpu::StatePointer(new gpu::State());
 
         // Mask out haze on the tablet
-        PrepareStencil::testNoAA(*state);
+        PrepareStencil::testMask(*state);
 
         gpu::Shader::BindingSet slotBindings;
         slotBindings.insert(gpu::Shader::Binding(std::string("hazeBuffer"), HazeEffect_ParamsSlot));
diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp
index 8b65c9f7ce..1ea3e1a705 100644
--- a/libraries/render-utils/src/MeshPartPayload.cpp
+++ b/libraries/render-utils/src/MeshPartPayload.cpp
@@ -337,7 +337,7 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in
     if (state.clusterMatrices.size() == 1) {
         renderTransform = transform.worldTransform(Transform(state.clusterMatrices[0]));
     }
-    updateTransformForSkinnedMesh(renderTransform, transform, state.clusterBuffer);
+    updateTransformForSkinnedMesh(renderTransform, transform);
 
     initCache();
 }
@@ -367,12 +367,25 @@ void ModelMeshPartPayload::notifyLocationChanged() {
 
 }
 
-void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& renderTransform, const Transform& boundTransform,
-        const gpu::BufferPointer& buffer) {
+
+void ModelMeshPartPayload::updateClusterBuffer(const std::vector<glm::mat4>& clusterMatrices) {
+    // Once computed the cluster matrices, update the buffer(s)
+    if (clusterMatrices.size() > 1) {
+        if (!_clusterBuffer) {
+            _clusterBuffer = std::make_shared<gpu::Buffer>(clusterMatrices.size() * sizeof(glm::mat4),
+                (const gpu::Byte*) clusterMatrices.data());
+        }
+        else {
+            _clusterBuffer->setSubData(0, clusterMatrices.size() * sizeof(glm::mat4),
+                (const gpu::Byte*) clusterMatrices.data());
+        }
+    }
+}
+
+void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& renderTransform, const Transform& boundTransform) {
     _transform = renderTransform;
     _worldBound = _adjustedLocalBound;
     _worldBound.transform(boundTransform);
-    _clusterBuffer = buffer;
 }
 
 ItemKey ModelMeshPartPayload::getKey() const {
@@ -416,7 +429,6 @@ int ModelMeshPartPayload::getLayer() const {
 }
 
 ShapeKey ModelMeshPartPayload::getShapeKey() const {
-
     // guard against partially loaded meshes
     ModelPointer model = _model.lock();
     if (!model || !model->isLoaded() || !model->getGeometry()) {
@@ -582,11 +594,11 @@ void ModelMeshPartPayload::render(RenderArgs* args) {
     args->_details._trianglesRendered += _drawPart._numIndices / INDICES_PER_TRIANGLE;
 }
 
-void ModelMeshPartPayload::computeAdjustedLocalBound(const QVector<glm::mat4>& clusterMatrices) {
+void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector<glm::mat4>& clusterMatrices) {
     _adjustedLocalBound = _localBound;
     if (clusterMatrices.size() > 0) {
         _adjustedLocalBound.transform(clusterMatrices[0]);
-        for (int i = 1; i < clusterMatrices.size(); ++i) {
+        for (int i = 1; i < (int)clusterMatrices.size(); ++i) {
             AABox clusterBound = _localBound;
             clusterBound.transform(clusterMatrices[i]);
             _adjustedLocalBound += clusterBound;
diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h
index 99c14510b5..971c6fe90b 100644
--- a/libraries/render-utils/src/MeshPartPayload.h
+++ b/libraries/render-utils/src/MeshPartPayload.h
@@ -87,9 +87,8 @@ public:
     typedef Payload::DataPointer Pointer;
 
     void notifyLocationChanged() override;
-    void updateTransformForSkinnedMesh(const Transform& renderTransform,
-            const Transform& boundTransform,
-            const gpu::BufferPointer& buffer);
+    void updateClusterBuffer(const std::vector<glm::mat4>& clusterMatrices);
+    void updateTransformForSkinnedMesh(const Transform& renderTransform, const Transform& boundTransform);
 
     // Render Item interface
     render::ItemKey getKey() const override;
@@ -103,7 +102,7 @@ public:
 
     void initCache();
 
-    void computeAdjustedLocalBound(const QVector<glm::mat4>& clusterMatrices);
+    void computeAdjustedLocalBound(const std::vector<glm::mat4>& clusterMatrices);
 
     gpu::BufferPointer _clusterBuffer;
     ModelWeakPointer _model;
diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp
index 97f62a3ce0..428fcc7a54 100644
--- a/libraries/render-utils/src/Model.cpp
+++ b/libraries/render-utils/src/Model.cpp
@@ -226,7 +226,7 @@ void Model::updateRenderItems() {
 
         // do nothing, if the model has already been destroyed.
         auto self = weakSelf.lock();
-        if (!self) {
+        if (!self || !self->isLoaded()) {
             return;
         }
 
@@ -237,24 +237,20 @@ void Model::updateRenderItems() {
         Transform modelTransform = self->getTransform();
         modelTransform.setScale(glm::vec3(1.0f));
 
-        uint32_t deleteGeometryCounter = self->_deleteGeometryCounter;
-
         render::Transaction transaction;
-        foreach (auto itemID, self->_modelMeshRenderItemsMap.keys()) {
-            transaction.updateItem<ModelMeshPartPayload>(itemID, [deleteGeometryCounter, modelTransform](ModelMeshPartPayload& data) {
-                ModelPointer model = data._model.lock();
-                if (model && model->isLoaded()) {
-                    // Ensure the model geometry was not reset between frames
-                    if (deleteGeometryCounter == model->_deleteGeometryCounter) {
+        for (int i = 0; i < (int) self->_modelMeshRenderItemIDs.size(); i++) {
 
-                        const Model::MeshState& state = model->getMeshState(data._meshIndex);
-                        Transform renderTransform = modelTransform;
-                        if (state.clusterMatrices.size() == 1) {
-                            renderTransform = modelTransform.worldTransform(Transform(state.clusterMatrices[0]));
-                        }
-                        data.updateTransformForSkinnedMesh(renderTransform, modelTransform, state.clusterBuffer);
-                    }
+            auto itemID = self->_modelMeshRenderItemIDs[i];
+            auto meshIndex = self->_modelMeshRenderItemShapes[i].meshIndex;
+            auto clusterMatrices(self->getMeshState(meshIndex).clusterMatrices);
+
+            transaction.updateItem<ModelMeshPartPayload>(itemID, [modelTransform, clusterMatrices](ModelMeshPartPayload& data) {
+                data.updateClusterBuffer(clusterMatrices);
+                Transform renderTransform = modelTransform;
+                if (clusterMatrices.size() == 1) {
+                    renderTransform = modelTransform.worldTransform(Transform(clusterMatrices[0]));
                 }
+                data.updateTransformForSkinnedMesh(renderTransform, modelTransform);
             });
         }
 
@@ -311,7 +307,7 @@ bool Model::updateGeometry() {
         foreach (const FBXMesh& mesh, fbxGeometry.meshes) {
             MeshState state;
             state.clusterMatrices.resize(mesh.clusters.size());
-            _meshStates.append(state);
+            _meshStates.push_back(state);
 
             // Note: we add empty buffers for meshes that lack blendshapes so we can access the buffers by index
             // later in ModelMeshPayload, however the vast majority of meshes will not have them.
@@ -686,6 +682,7 @@ void Model::removeFromScene(const render::ScenePointer& scene, render::Transacti
     _modelMeshRenderItemIDs.clear();
     _modelMeshRenderItemsMap.clear();
     _modelMeshRenderItems.clear();
+    _modelMeshRenderItemShapes.clear();
 
     foreach(auto item, _collisionRenderItemsMap.keys()) {
         transaction.removeItem(item);
@@ -1127,7 +1124,7 @@ void Model::updateClusterMatrices() {
     }
     _needsUpdateClusterMatrices = false;
     const FBXGeometry& geometry = getFBXGeometry();
-    for (int i = 0; i < _meshStates.size(); i++) {
+    for (int i = 0; i < (int) _meshStates.size(); i++) {
         MeshState& state = _meshStates[i];
         const FBXMesh& mesh = geometry.meshes.at(i);
         for (int j = 0; j < mesh.clusters.size(); j++) {
@@ -1135,17 +1132,6 @@ void Model::updateClusterMatrices() {
             auto jointMatrix = _rig.getJointTransform(cluster.jointIndex);
             glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
         }
-
-        // Once computed the cluster matrices, update the buffer(s)
-        if (mesh.clusters.size() > 1) {
-            if (!state.clusterBuffer) {
-                state.clusterBuffer = std::make_shared<gpu::Buffer>(state.clusterMatrices.size() * sizeof(glm::mat4),
-                                                                    (const gpu::Byte*) state.clusterMatrices.constData());
-            } else {
-                state.clusterBuffer->setSubData(0, state.clusterMatrices.size() * sizeof(glm::mat4),
-                                                (const gpu::Byte*) state.clusterMatrices.constData());
-            }
-        }
     }
 
     // post the blender if we're not currently waiting for one to finish
@@ -1255,7 +1241,7 @@ void Model::createVisibleRenderItemSet() {
     const auto& meshes = _renderGeometry->getMeshes();
 
     // all of our mesh vectors must match in size
-    if ((int)meshes.size() != _meshStates.size()) {
+    if (meshes.size() != _meshStates.size()) {
         qCDebug(renderutils) << "WARNING!!!! Mesh Sizes don't match! We will not segregate mesh groups yet.";
         return;
     }
@@ -1264,6 +1250,7 @@ void Model::createVisibleRenderItemSet() {
     Q_ASSERT(_modelMeshRenderItems.isEmpty());
 
     _modelMeshRenderItems.clear();
+    _modelMeshRenderItemShapes.clear();
 
     Transform transform;
     transform.setTranslation(_translation);
@@ -1286,6 +1273,7 @@ void Model::createVisibleRenderItemSet() {
         int numParts = (int)mesh->getNumParts();
         for (int partIndex = 0; partIndex < numParts; partIndex++) {
             _modelMeshRenderItems << std::make_shared<ModelMeshPartPayload>(shared_from_this(), i, partIndex, shapeID, transform, offset);
+            _modelMeshRenderItemShapes.emplace_back(ShapeInfo{ (int)i });
             shapeID++;
         }
     }
@@ -1327,7 +1315,7 @@ void Model::createCollisionRenderItemSet() {
 }
 
 bool Model::isRenderable() const {
-    return !_meshStates.isEmpty() || (isLoaded() && _renderGeometry->getMeshes().empty());
+    return !_meshStates.empty() || (isLoaded() && _renderGeometry->getMeshes().empty());
 }
 
 class CollisionRenderGeometry : public Geometry {
diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h
index 3abf7e2758..c537a928b3 100644
--- a/libraries/render-utils/src/Model.h
+++ b/libraries/render-utils/src/Model.h
@@ -246,8 +246,7 @@ public:
 
     class MeshState {
     public:
-        QVector<glm::mat4> clusterMatrices;
-        gpu::BufferPointer clusterBuffer;
+        std::vector<glm::mat4> clusterMatrices;
     };
 
     const MeshState& getMeshState(int index) { return _meshStates.at(index); }
@@ -317,7 +316,7 @@ protected:
     bool _snappedToRegistrationPoint; /// are we currently snapped to a registration point
     glm::vec3 _registrationPoint = glm::vec3(0.5f); /// the point in model space our center is snapped to
 
-    QVector<MeshState> _meshStates;
+    std::vector<MeshState> _meshStates;
 
     virtual void initJointStates();
 
@@ -388,8 +387,9 @@ protected:
 
     QVector<std::shared_ptr<ModelMeshPartPayload>> _modelMeshRenderItems;
     QMap<render::ItemID, render::PayloadPointer> _modelMeshRenderItemsMap;
-
     render::ItemIDs _modelMeshRenderItemIDs;
+    using ShapeInfo = struct { int meshIndex; };
+    std::vector<ShapeInfo> _modelMeshRenderItemShapes;
 
     bool _addedToScene { false }; // has been added to scene
     bool _needsFixupInScene { true }; // needs to be removed/re-added to scene
diff --git a/libraries/render-utils/src/SoftAttachmentModel.cpp b/libraries/render-utils/src/SoftAttachmentModel.cpp
index 63b18d49b8..63991f9422 100644
--- a/libraries/render-utils/src/SoftAttachmentModel.cpp
+++ b/libraries/render-utils/src/SoftAttachmentModel.cpp
@@ -43,7 +43,7 @@ void SoftAttachmentModel::updateClusterMatrices() {
 
     const FBXGeometry& geometry = getFBXGeometry();
 
-    for (int i = 0; i < _meshStates.size(); i++) {
+    for (int i = 0; i < (int) _meshStates.size(); i++) {
         MeshState& state = _meshStates[i];
         const FBXMesh& mesh = geometry.meshes.at(i);
 
@@ -60,17 +60,6 @@ void SoftAttachmentModel::updateClusterMatrices() {
             }
             glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
         }
-
-        // Once computed the cluster matrices, update the buffer(s)
-        if (mesh.clusters.size() > 1) {
-            if (!state.clusterBuffer) {
-                state.clusterBuffer = std::make_shared<gpu::Buffer>(state.clusterMatrices.size() * sizeof(glm::mat4),
-                                                                    (const gpu::Byte*) state.clusterMatrices.constData());
-            } else {
-                state.clusterBuffer->setSubData(0, state.clusterMatrices.size() * sizeof(glm::mat4),
-                                                (const gpu::Byte*) state.clusterMatrices.constData());
-            }
-        }
     }
 
     // post the blender if we're not currently waiting for one to finish
diff --git a/libraries/render-utils/src/StencilMaskPass.cpp b/libraries/render-utils/src/StencilMaskPass.cpp
index 0e0d8b56b3..f71111b64e 100644
--- a/libraries/render-utils/src/StencilMaskPass.cpp
+++ b/libraries/render-utils/src/StencilMaskPass.cpp
@@ -116,9 +116,9 @@ void PrepareStencil::drawBackground(gpu::State& state) {
         gpu::State::STENCIL_OP_REPLACE, gpu::State::STENCIL_OP_REPLACE, gpu::State::STENCIL_OP_KEEP));
 }
 
-// Pass if this area has NOT been marked as MASK
+// Pass if this area has NOT been marked as MASK or anything containing MASK
 void PrepareStencil::testMask(gpu::State& state) {
-    state.setStencilTest(true, 0x00, gpu::State::StencilTest(STENCIL_MASK, 0xFF, gpu::NOT_EQUAL,
+    state.setStencilTest(true, 0x00, gpu::State::StencilTest(STENCIL_MASK, STENCIL_MASK, gpu::NOT_EQUAL,
         gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
 }
 
diff --git a/libraries/render-utils/src/StencilMaskPass.h b/libraries/render-utils/src/StencilMaskPass.h
index ddbf4a7ac0..fc258b607a 100644
--- a/libraries/render-utils/src/StencilMaskPass.h
+++ b/libraries/render-utils/src/StencilMaskPass.h
@@ -49,8 +49,8 @@ public:
 
     static void drawMask(gpu::State& state);
     static void drawBackground(gpu::State& state);
-    static void testNoAA(gpu::State& state);
     static void testMask(gpu::State& state);
+    static void testNoAA(gpu::State& state);
     static void testBackground(gpu::State& state);
     static void testShape(gpu::State& state);
     static void testMaskDrawShape(gpu::State& state);
diff --git a/libraries/shared/src/HifiConfigVariantMap.cpp b/libraries/shared/src/HifiConfigVariantMap.cpp
index d0fb14e104..59f660feee 100644
--- a/libraries/shared/src/HifiConfigVariantMap.cpp
+++ b/libraries/shared/src/HifiConfigVariantMap.cpp
@@ -91,32 +91,6 @@ QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringL
     return mergedMap;
 }
 
-HifiConfigVariantMap::HifiConfigVariantMap() :
-    _userConfigFilename(),
-    _masterConfig(),
-    _userConfig(),
-    _mergedConfig()
-{
-
-}
-
-void HifiConfigVariantMap::loadMasterAndUserConfig(const QStringList& argumentList) {
-    // check if there is a master config file
-    const QString MASTER_CONFIG_FILE_OPTION = "--master-config";
-
-    int masterConfigIndex = argumentList.indexOf(MASTER_CONFIG_FILE_OPTION);
-    if (masterConfigIndex != -1) {
-        QString masterConfigFilepath = argumentList[masterConfigIndex + 1];
-
-        loadMapFromJSONFile(_masterConfig, masterConfigFilepath);
-    }
-
-    // load the user config - that method replace loadMasterAndUserConfig after the 1.7 migration
-    loadConfig(argumentList);
-
-    mergeMasterAndUserConfigs();
-}
-
 void HifiConfigVariantMap::loadConfig(const QStringList& argumentList) {
     // load the user config
     const QString USER_CONFIG_FILE_OPTION = "--user-config";
@@ -172,14 +146,6 @@ void HifiConfigVariantMap::loadConfig(const QStringList& argumentList) {
     loadMapFromJSONFile(_userConfig, _userConfigFilename);
 }
 
-void HifiConfigVariantMap::mergeMasterAndUserConfigs() {
-    // the merged config is initially matched to the master config
-    _mergedConfig = _masterConfig;
-
-    // then we merge in anything missing from the user config
-    addMissingValuesToExistingMap(_mergedConfig, _userConfig);
-}
-
 void HifiConfigVariantMap::loadMapFromJSONFile(QVariantMap& existingMap, const QString& filename) {
     QFile configFile(filename);
 
diff --git a/libraries/shared/src/HifiConfigVariantMap.h b/libraries/shared/src/HifiConfigVariantMap.h
index cb6e92df96..ee248ec3d2 100644
--- a/libraries/shared/src/HifiConfigVariantMap.h
+++ b/libraries/shared/src/HifiConfigVariantMap.h
@@ -21,26 +21,19 @@ class HifiConfigVariantMap {
 public:
     static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList);
 
-    HifiConfigVariantMap();
-    void loadMasterAndUserConfig(const QStringList& argumentList);
     void loadConfig(const QStringList& argumentList);
 
     const QVariant value(const QString& key) const { return _userConfig.value(key); }
     QVariant* valueForKeyPath(const QString& keyPath, bool shouldCreateIfMissing = false)
         { return ::valueForKeyPath(_userConfig, keyPath, shouldCreateIfMissing); }
 
-    QVariantMap& getMergedConfig() { return _mergedConfig; }
     QVariantMap& getConfig() { return _userConfig; }
 
-    void mergeMasterAndUserConfigs();
-
     const QString& getUserConfigFilename() const { return _userConfigFilename; }
 private:
     QString _userConfigFilename;
 
-    QVariantMap _masterConfig;
     QVariantMap _userConfig;
-    QVariantMap _mergedConfig;
 
     void loadMapFromJSONFile(QVariantMap& existingMap, const QString& filename);
     void addMissingValuesToExistingMap(QVariantMap& existingMap, const QVariantMap& newMap);
diff --git a/libraries/shared/src/SettingHelpers.cpp b/libraries/shared/src/SettingHelpers.cpp
index dd301aa5aa..c6b8ac8f83 100644
--- a/libraries/shared/src/SettingHelpers.cpp
+++ b/libraries/shared/src/SettingHelpers.cpp
@@ -23,9 +23,15 @@
 
 #include "SharedLogging.h"
 
+const QSettings::Format JSON_FORMAT = QSettings::registerFormat("json", readJSONFile, writeJSONFile);
+
 QSettings::SettingsMap jsonDocumentToVariantMap(const QJsonDocument& document);
 QJsonDocument variantMapToJsonDocument(const QSettings::SettingsMap& map);
 
+QString settingsFilename() {
+    return QSettings().fileName();
+}
+
 bool readJSONFile(QIODevice& device, QSettings::SettingsMap& map) {
     QJsonParseError jsonParseError;
 
diff --git a/libraries/shared/src/SettingHelpers.h b/libraries/shared/src/SettingHelpers.h
index 122e56957c..ad61897c65 100644
--- a/libraries/shared/src/SettingHelpers.h
+++ b/libraries/shared/src/SettingHelpers.h
@@ -14,12 +14,13 @@
 
 #include <QSettings>
 
+extern const QSettings::Format JSON_FORMAT;
+
+QString settingsFilename();
+
 bool readJSONFile(QIODevice& device, QSettings::SettingsMap& map);
 bool writeJSONFile(QIODevice& device, const QSettings::SettingsMap& map);
 
-static const auto JSON_FORMAT = QSettings::registerFormat("json", readJSONFile, writeJSONFile);
-
 void loadOldINIFile(QSettings& settings);
 
-
 #endif // hifi_SettingHelpers_h
diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js
index e34855d521..7d9a6dc1b5 100644
--- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js
+++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js
@@ -13,12 +13,13 @@
    makeDispatcherModuleParameters, MSECS_PER_SEC, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION,
    PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD,
    DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, ensureDynamic,
-   getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, Reticle, Overlays, isPointingAtUI
+   getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, Reticle, Overlays, isPointingAtUI, Xform, getEntityParents
 
 */
 
 Script.include("/~/system/libraries/controllerDispatcherUtils.js");
 Script.include("/~/system/libraries/controllers.js");
+Script.include("/~/system/libraries/Xform.js");
 
 (function() {
     var PICK_WITH_HAND_RAY = true;
@@ -113,18 +114,71 @@ Script.include("/~/system/libraries/controllers.js");
     ];
 
     var MARGIN = 25;
+
+    function TargetObject(entityID, entityProps) {
+        this.entityID = entityID;
+        this.entityProps = entityProps;
+        this.targetEntityID = null;
+        this.targetEntityProps = null;
+        this.previousCollisionStatus = null;
+        this.madeDynamic = null;
+
+        this.makeDynamic = function() {
+            if (this.targetEntityID) {
+                var newProps = {
+                    dynamic: true,
+                    collisionless: true
+                };
+                this.previousCollisionStatus = this.targetEntityProps.collisionless;
+                Entities.editEntity(this.targetEntityID, newProps);
+                this.madeDynamic = true;
+            }
+        };
+
+        this.restoreTargetEntityOriginalProps = function() {
+            if (this.madeDynamic) {
+                var props = {};
+                props.dynamic = false;
+                props.collisionless = this.previousCollisionStatus;
+                var zeroVector = {x: 0, y: 0, z:0};
+                props.localVelocity = zeroVector;
+                props.localRotation = zeroVector;
+                Entities.editEntity(this.targetEntityID, props);
+            }
+        };
+
+        this.getTargetEntity = function() {
+            var parentPropsLength = this.parentProps.length;
+            if (parentPropsLength !== 0) {
+                var targetEntity = {
+                    id: this.parentProps[parentPropsLength - 1].id,
+                    props: this.parentProps[parentPropsLength - 1]};
+                this.targetEntityID = targetEntity.id;
+                this.targetEntityProps = targetEntity.props;
+                return targetEntity;
+            }
+            this.targetEntityID = this.entityID;
+            this.targetEntityProps = this.entityProps;
+            return {
+                id: this.entityID,
+                props: this.entityProps};
+        };
+    }
+
     function FarActionGrabEntity(hand) {
         this.hand = hand;
         this.grabbedThingID = null;
+        this.targetObject = null;
         this.actionID = null; // action this script created...
+        this.entityToLockOnto = null;
         this.entityWithContextOverlay = false;
         this.contextOverlayTimer = false;
         this.previousCollisionStatus = false;
+        this.locked = false;
         this.reticleMinX = MARGIN;
         this.reticleMaxX;
         this.reticleMinY = MARGIN;
         this.reticleMaxY;
-        this.madeDynamic = false;
 
         var ACTION_TTL = 15; // seconds
 
@@ -158,9 +212,25 @@ Script.include("/~/system/libraries/controllers.js");
             LaserPointers.enableLaserPointer(laserPointerID);
             LaserPointers.setRenderState(laserPointerID, mode);
             if (this.distanceHolding || this.distanceRotating) {
-                LaserPointers.setLockEndUUID(laserPointerID, this.grabbedThingID, this.grabbedIsOverlay);
+                if (!this.locked) {
+                    // calculate offset
+                    var targetProps = Entities.getEntityProperties(this.targetObject.entityID, [
+                        "position",
+                        "rotation"
+                    ]);
+                    var zeroVector = { x: 0, y: 0, z:0, w: 0 };
+                    var intersection = controllerData.rayPicks[this.hand].intersection;
+                    var intersectionMat = new Xform(zeroVector, intersection);
+                    var modelMat = new Xform(targetProps.rotation, targetProps.position);
+                    var modelMatInv = modelMat.inv();
+                    var xformMat = Xform.mul(modelMatInv, intersectionMat);
+                    var offsetMat = Mat4.createFromRotAndTrans(xformMat.rot, xformMat.pos);
+                    LaserPointers.setLockEndUUID(laserPointerID, this.targetObject.entityID, this.grabbedIsOverlay, offsetMat);
+                    this.locked = true;
+                }
             } else {
                 LaserPointers.setLockEndUUID(laserPointerID, null, false);
+                this.locked = false;
             }
         };
 
@@ -339,21 +409,15 @@ Script.include("/~/system/libraries/controllers.js");
 
             var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
             Entities.callEntityMethod(this.grabbedThingID, "releaseGrab", args);
-
-            if (this.madeDynamic) {
-                var props = {};
-                props.dynamic = false;
-                props.collisionless = this.previousCollisionStatus;
-                props.localVelocity = {x: 0, y: 0, z: 0};
-                props.localRotation = {x: 0, y: 0, z: 0};
-                Entities.editEntity(this.grabbedThingID, props);
-                this.madeDynamic = false;
+            if (this.targetObject) {
+                this.targetObject.restoreTargetEntityOriginalProps();
             }
             this.actionID = null;
             this.grabbedThingID = null;
+            this.targetObject = null;
         };
 
-         this.updateRecommendedArea = function() {
+        this.updateRecommendedArea = function() {
             var dims = Controller.getViewportDimensions();
             this.reticleMaxX = dims.x - MARGIN;
             this.reticleMaxY = dims.y - MARGIN;
@@ -503,17 +567,18 @@ Script.include("/~/system/libraries/controllers.js");
                             "userData", "locked", "type"
                         ]);
 
+                        this.targetObject = new TargetObject(entityID, targetProps);
+                        this.targetObject.parentProps = getEntityParents(targetProps);
                         if (entityID !== this.entityWithContextOverlay) {
                             this.destroyContextOverlay();
                         }
+                        var targetEntity = this.targetObject.getTargetEntity();
+                        entityID = targetEntity.id;
+                        targetProps = targetEntity.props;
 
                         if (entityIsGrabbable(targetProps)) {
                             if (!entityIsDistanceGrabbable(targetProps)) {
-                                targetProps.dynamic = true;
-                                this.previousCollisionStatus = targetProps.collisionless;
-                                targetProps.collisionless = true;
-                                Entities.editEntity(entityID, targetProps);
-                                this.madeDynamic = true;
+                                this.targetObject.makeDynamic();
                             }
 
                             if (!this.distanceRotating) {
diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js
index a1846e7ad7..eaa15ee3b1 100644
--- a/scripts/system/controllers/grab.js
+++ b/scripts/system/controllers/grab.js
@@ -315,6 +315,10 @@ Grabber.prototype.pressEvent = function(event) {
         return;
     }
 
+    if (event.isAlt || event.isMeta) {
+        return;
+    }
+
     if (Overlays.getOverlayAtPoint(Reticle.position) > 0) {
         // the mouse is pointing at an overlay; don't look for entities underneath the overlay.
         return;
diff --git a/scripts/system/edit.js b/scripts/system/edit.js
index e76a02b6f5..15f1c2f6c1 100644
--- a/scripts/system/edit.js
+++ b/scripts/system/edit.js
@@ -1333,7 +1333,7 @@ function sortSelectedEntities(selected) {
     return sortedEntities;
 }
 
-function recursiveDelete(entities, childrenList) {
+function recursiveDelete(entities, childrenList, deletedIDs) {
     var entitiesLength = entities.length;
     for (var i = 0; i < entitiesLength; i++) {
         var entityID = entities[i];
@@ -1346,6 +1346,7 @@ function recursiveDelete(entities, childrenList) {
             properties: initialProperties,
             children: grandchildrenList
         });
+        deletedIDs.push(entityID);
         Entities.deleteEntity(entityID);
     }
 }
@@ -1413,6 +1414,8 @@ function parentSelectedEntities() {
 }
 function deleteSelectedEntities() {
     if (SelectionManager.hasSelection()) {
+        var deletedIDs = [];
+
         selectedParticleEntityID = null;
         particleExplorerTool.destroyWebView();
         SelectionManager.saveProperties();
@@ -1423,16 +1426,22 @@ function deleteSelectedEntities() {
             var initialProperties = SelectionManager.savedProperties[entityID];
             var children = Entities.getChildrenIDs(entityID);
             var childList = [];
-            recursiveDelete(children, childList);
+            recursiveDelete(children, childList, deletedIDs);
             savedProperties.push({
                 entityID: entityID,
                 properties: initialProperties,
                 children: childList
             });
+            deletedIDs.push(entityID);
             Entities.deleteEntity(entityID);
         }
         SelectionManager.clearSelections();
         pushCommandForSelections([], savedProperties);
+
+        entityListTool.webView.emitScriptEvent(JSON.stringify({
+            type: "deleted",
+            ids: deletedIDs
+        }));
     }
 }
 
@@ -1533,7 +1542,7 @@ function handeMenuEvent(menuItem) {
             Window.openFileChanged.connect(onFileOpenChanged);
             Window.browseAsync("Select Model to Import", "", "*.json");
         } else {
-            Window.promptTextChanged.connect(onFileOpenChanged);
+            Window.promptTextChanged.connect(onPromptTextChanged);
             Window.promptAsync("URL of SVO to import", "");
         }
     } else if (menuItem === "Entity List...") {
diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js
index ea79750154..7b25e66c67 100644
--- a/scripts/system/html/js/entityList.js
+++ b/scripts/system/html/js/entityList.js
@@ -286,7 +286,6 @@ function loaded() {
       }
       elDelete.onclick = function() {
           EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' }));
-          refreshEntities();
       }
 
       document.addEventListener("keydown", function (keyDownEvent) {
@@ -362,6 +361,12 @@ function loaded() {
                       updateSelectedEntities(data.selectedIDs);
                       resize();
                   }
+              } else if (data.type === "deleted") {
+                  for (i = 0, length = data.ids.length; i < length; i++) {
+                      delete entities[data.ids[i]];
+                      entityList.remove("id", data.ids[i]);
+                  }
+                  refreshFooter();
               }
           });
           setTimeout(refreshEntities, 1000);
diff --git a/scripts/system/libraries/cloneEntityUtils.js b/scripts/system/libraries/cloneEntityUtils.js
index 777504b16d..63b161eb80 100644
--- a/scripts/system/libraries/cloneEntityUtils.js
+++ b/scripts/system/libraries/cloneEntityUtils.js
@@ -8,8 +8,7 @@
 /* global entityIsCloneable:true, getGrabbableData:true, cloneEntity:true, propsAreCloneDynamic:true, Script,
    propsAreCloneDynamic:true, Entities*/
 
-Script.include("/~/system/controllers/controllerDispatcherUtils.js");
-
+Script.include("/~/system/libraries/controllerDispatcherUtils.js");
 
 // Object assign  polyfill
 if (typeof Object.assign !== 'function') {
diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js
index cd3f1a711f..fb6de0e683 100644
--- a/scripts/system/libraries/controllerDispatcherUtils.js
+++ b/scripts/system/libraries/controllerDispatcherUtils.js
@@ -35,12 +35,14 @@
    propsArePhysical:true,
    controllerDispatcherPluginsNeedSort:true,
    projectOntoXYPlane:true,
+   getChildrenProps:true,
    projectOntoEntityXYPlane:true,
    projectOntoOverlayXYPlane:true,
    entityHasActions:true,
    ensureDynamic:true,
    findGroupParent:true,
    BUMPER_ON_VALUE:true,
+   getEntityParents:true,
    findHandChildEntities:true,
    TEAR_AWAY_DISTANCE:true,
    TEAR_AWAY_COUNT:true,
@@ -306,6 +308,23 @@ findGroupParent = function (controllerData, targetProps) {
     return targetProps;
 };
 
+getEntityParents = function(targetProps) {
+    var parentProperties = [];
+    while (targetProps.parentID &&
+           targetProps.parentID !== Uuid.NULL &&
+           Entities.getNestableType(targetProps.parentID) == "entity") {
+        var parentProps = Entities.getEntityProperties(targetProps.parentID, DISPATCHER_PROPERTIES);
+        if (!parentProps) {
+            break;
+        }
+        parentProps.id = targetProps.parentID;
+        targetProps = parentProps;
+        parentProperties.push(parentProps);
+    }
+
+    return parentProperties;
+};
+
 
 findHandChildEntities = function(hand) {
     // find children of avatar's hand joint
diff --git a/tests/octree/src/OctreeTests.cpp b/tests/octree/src/OctreeTests.cpp
index 64d68e8e13..81300a1293 100644
--- a/tests/octree/src/OctreeTests.cpp
+++ b/tests/octree/src/OctreeTests.cpp
@@ -74,7 +74,7 @@ void OctreeTests::propertyFlagsTests() {
         EntityPropertyFlags props;
         props.setHasProperty(PROP_VISIBLE);
         props.setHasProperty(PROP_POSITION);
-        props.setHasProperty(PROP_RADIUS);
+        props.setHasProperty(PROP_DIMENSIONS);
         props.setHasProperty(PROP_MODEL_URL);
         props.setHasProperty(PROP_COMPOUND_SHAPE_URL);
         props.setHasProperty(PROP_ROTATION);