diff --git a/CMakeLists.txt b/CMakeLists.txt
index 406fb5aeb1..3c90256134 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -208,9 +208,10 @@ foreach(CUSTOM_MACRO ${HIFI_CUSTOM_MACROS})
   include(${CUSTOM_MACRO})
 endforeach()
 
-file(GLOB_RECURSE JS_SRC scripts/*.js)
+file(GLOB_RECURSE JS_SRC scripts/*.js unpublishedScripts/*.js)
 add_custom_target(js SOURCES ${JS_SRC})
 GroupSources("scripts")
+GroupSources("unpublishedScripts")
 
 if (UNIX)
    install(
diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp
index 469ca9f178..47836727fe 100644
--- a/assignment-client/src/Agent.cpp
+++ b/assignment-client/src/Agent.cpp
@@ -592,6 +592,7 @@ void Agent::setIsAvatar(bool isAvatar) {
 void Agent::sendAvatarIdentityPacket() {
     if (_isAvatar) {
         auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
+        scriptedAvatar->markIdentityDataChanged();
         scriptedAvatar->sendIdentityPacket();
     }
 }
diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp
index b95c429b2d..bb03a6ec93 100644
--- a/assignment-client/src/audio/AudioMixer.cpp
+++ b/assignment-client/src/audio/AudioMixer.cpp
@@ -38,13 +38,14 @@
 #include "AudioMixer.h"
 
 static const float DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE = 0.5f;    // attenuation = -6dB * log2(distance)
+static const int DISABLE_STATIC_JITTER_FRAMES = -1;
 static const float DEFAULT_NOISE_MUTING_THRESHOLD = 1.0f;
 static const QString AUDIO_MIXER_LOGGING_TARGET_NAME = "audio-mixer";
 static const QString AUDIO_ENV_GROUP_KEY = "audio_env";
 static const QString AUDIO_BUFFER_GROUP_KEY = "audio_buffer";
 static const QString AUDIO_THREADING_GROUP_KEY = "audio_threading";
 
-int AudioMixer::_numStaticJitterFrames{ -1 };
+int AudioMixer::_numStaticJitterFrames{ DISABLE_STATIC_JITTER_FRAMES };
 float AudioMixer::_noiseMutingThreshold{ DEFAULT_NOISE_MUTING_THRESHOLD };
 float AudioMixer::_attenuationPerDoublingInDistance{ DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE };
 std::map<QString, std::shared_ptr<CodecPlugin>> AudioMixer::_availableCodecs{ };
@@ -56,7 +57,12 @@ QVector<AudioMixer::ReverbSettings> AudioMixer::_zoneReverbSettings;
 AudioMixer::AudioMixer(ReceivedMessage& message) :
     ThreadedAssignment(message) {
 
+    // Always clear settings first
+    // This prevents previous assignment settings from sticking around
+    clearDomainSettings();
+
     // hash the available codecs (on the mixer)
+    _availableCodecs.clear(); // Make sure struct is clean
     auto codecPlugins = PluginManager::getInstance()->getCodecPlugins();
     std::for_each(codecPlugins.cbegin(), codecPlugins.cend(),
         [&](const CodecPluginPointer& codec) {
@@ -232,7 +238,7 @@ void AudioMixer::sendStatsPacket() {
     }
 
     // general stats
-    statsObject["useDynamicJitterBuffers"] = _numStaticJitterFrames == -1;
+    statsObject["useDynamicJitterBuffers"] = _numStaticJitterFrames == DISABLE_STATIC_JITTER_FRAMES;
 
     statsObject["threads"] = _slavePool.numThreads();
 
@@ -490,6 +496,16 @@ int AudioMixer::prepareFrame(const SharedNodePointer& node, unsigned int frame)
     return data->checkBuffersBeforeFrameSend();
 }
 
+void AudioMixer::clearDomainSettings() {
+    _numStaticJitterFrames = DISABLE_STATIC_JITTER_FRAMES;
+    _attenuationPerDoublingInDistance = DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE;
+    _noiseMutingThreshold = DEFAULT_NOISE_MUTING_THRESHOLD;
+    _codecPreferenceOrder.clear();
+    _audioZones.clear();
+    _zoneSettings.clear();
+    _zoneReverbSettings.clear();
+}
+
 void AudioMixer::parseSettingsObject(const QJsonObject &settingsObject) {
     qDebug() << "AVX2 Support:" << (cpuSupportsAVX2() ? "enabled" : "disabled");
 
@@ -525,7 +541,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject &settingsObject) {
             qDebug() << "Static desired jitter buffer frames:" << _numStaticJitterFrames;
         } else {
             qDebug() << "Disabling dynamic jitter buffers.";
-            _numStaticJitterFrames = -1;
+            _numStaticJitterFrames = DISABLE_STATIC_JITTER_FRAMES;
         }
 
         // check for deprecated audio settings
diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h
index 0641c04a6c..18e754016e 100644
--- a/assignment-client/src/audio/AudioMixer.h
+++ b/assignment-client/src/audio/AudioMixer.h
@@ -79,6 +79,7 @@ private:
     QString percentageForMixStats(int counter);
 
     void parseSettingsObject(const QJsonObject& settingsObject);
+    void clearDomainSettings();
 
     float _trailingMixRatio { 0.0f };
     float _throttlingRatio { 0.0f };
diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp
index 15a7f50fa3..4d80bc7d17 100644
--- a/assignment-client/src/avatars/AvatarMixerClientData.cpp
+++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp
@@ -16,7 +16,14 @@
 
 #include "AvatarMixerClientData.h"
 
+AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID) :
+    NodeData(nodeID)
+{
+    _currentViewFrustum.invalidate();
 
+    // in case somebody calls getSessionUUID on the AvatarData instance, make sure it has the right ID
+    _avatar->setID(nodeID);
+}
 
 void AvatarMixerClientData::queuePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node) {
     if (!_packetQueue.node) {
diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h
index 76519466b5..c905b10251 100644
--- a/assignment-client/src/avatars/AvatarMixerClientData.h
+++ b/assignment-client/src/avatars/AvatarMixerClientData.h
@@ -36,7 +36,7 @@ const QString INBOUND_AVATAR_DATA_STATS_KEY = "inbound_av_data_kbps";
 class AvatarMixerClientData : public NodeData {
     Q_OBJECT
 public:
-    AvatarMixerClientData(const QUuid& nodeID = QUuid()) : NodeData(nodeID) { _currentViewFrustum.invalidate(); }
+    AvatarMixerClientData(const QUuid& nodeID = QUuid());
     virtual ~AvatarMixerClientData() {}
     using HRCTime = p_high_resolution_clock::time_point;
 
diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json
index b18599d8a9..f88e322d99 100644
--- a/interface/resources/avatar/avatar-animation.json
+++ b/interface/resources/avatar/avatar-animation.json
@@ -68,7 +68,7 @@
                                     "typeVar": "rightHandType",
                                     "weightVar": "rightHandWeight",
                                     "weight": 1.0,
-                                    "flexCoefficients": [1, 0.5, 0.5, 0.25, 0.1, 0.05, 0.01, 0.0, 0.0]
+                                    "flexCoefficients": [1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0]
                                 },
                                 {
                                     "jointName": "LeftHand",
@@ -77,7 +77,7 @@
                                     "typeVar": "leftHandType",
                                     "weightVar": "leftHandWeight",
                                     "weight": 1.0,
-                                    "flexCoefficients": [1, 0.5, 0.5, 0.25, 0.1, 0.05, 0.01, 0.0, 0.0]
+                                    "flexCoefficients": [1, 0.5, 0.5, 0.2, 0.01, 0.005, 0.001, 0.0, 0.0]
                                 },
                                 {
                                     "jointName": "RightFoot",
@@ -104,7 +104,7 @@
                                     "typeVar": "spine2Type",
                                     "weightVar": "spine2Weight",
                                     "weight": 1.0,
-                                    "flexCoefficients": [0.45, 0.45]
+                                    "flexCoefficients": [1.0, 0.5, 0.5]
                                 },
                                 {
                                     "jointName": "Head",
@@ -113,7 +113,7 @@
                                     "typeVar": "headType",
                                     "weightVar": "headWeight",
                                     "weight": 4.0,
-                                    "flexCoefficients": [1, 0.5, 0.5, 0.5, 0.5]
+                                    "flexCoefficients": [1, 0.05, 0.25, 0.25, 0.25]
                                 }
                             ]
                         },
diff --git a/interface/resources/icons/loader-snake-64-w.gif b/interface/resources/icons/loader-snake-64-w.gif
new file mode 100644
index 0000000000..e6594ab0a2
Binary files /dev/null and b/interface/resources/icons/loader-snake-64-w.gif differ
diff --git a/interface/resources/images/Default-Sky-9-cubemap.ktx b/interface/resources/images/Default-Sky-9-cubemap.ktx
index 476d381a8c..70f495c3ab 100644
Binary files a/interface/resources/images/Default-Sky-9-cubemap.ktx and b/interface/resources/images/Default-Sky-9-cubemap.ktx differ
diff --git a/interface/resources/qml/TabletBrowser.qml b/interface/resources/qml/TabletBrowser.qml
index 74318a165e..ee4d05a701 100644
--- a/interface/resources/qml/TabletBrowser.qml
+++ b/interface/resources/qml/TabletBrowser.qml
@@ -4,6 +4,7 @@ import QtWebChannel 1.0
 import QtWebEngine 1.2
 
 import "controls"
+import "controls-uit" as HifiControls
 import "styles" as HifiStyles
 import "styles-uit"
 import "windows"
@@ -117,6 +118,8 @@ Item {
         onNewViewRequested: {
             request.openIn(webView);
         }
+
+        HifiControls.WebSpinner { }
     }
 
     Keys.onPressed: {
diff --git a/interface/resources/qml/controls-uit/BaseWebView.qml b/interface/resources/qml/controls-uit/BaseWebView.qml
index 9c22a8ff8c..670aea71aa 100644
--- a/interface/resources/qml/controls-uit/BaseWebView.qml
+++ b/interface/resources/qml/controls-uit/BaseWebView.qml
@@ -36,4 +36,6 @@ WebEngineView {
             }
         }
     }
+
+    WebSpinner { }
 }
diff --git a/interface/resources/qml/controls-uit/WebSpinner.qml b/interface/resources/qml/controls-uit/WebSpinner.qml
new file mode 100644
index 0000000000..6323bff1a7
--- /dev/null
+++ b/interface/resources/qml/controls-uit/WebSpinner.qml
@@ -0,0 +1,21 @@
+//
+//  WebSpinner.qml
+//
+//  Created by David Rowe on 23 May 2017
+//  Copyright 2017 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import QtQuick 2.5
+
+AnimatedImage {
+    source: "../../icons/loader-snake-64-w.gif"
+    visible: parent.loading && /^(http.*|)$/i.test(parent.url.toString())
+    z: 10000
+    anchors {
+        horizontalCenter: parent.horizontalCenter
+        verticalCenter: parent.verticalCenter
+    }
+}
diff --git a/interface/resources/qml/controls/TabletWebScreen.qml b/interface/resources/qml/controls/TabletWebScreen.qml
index fec91046d8..93ded724a1 100644
--- a/interface/resources/qml/controls/TabletWebScreen.qml
+++ b/interface/resources/qml/controls/TabletWebScreen.qml
@@ -116,6 +116,8 @@ Item {
                 tabletRoot.openBrowserWindow(request, profile);
             }
         }
+
+        HiFiControls.WebSpinner { }
     }
 
     HiFiControls.Keyboard {
diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml
index d939e088a8..215ac68ac0 100644
--- a/interface/resources/qml/controls/TabletWebView.qml
+++ b/interface/resources/qml/controls/TabletWebView.qml
@@ -238,6 +238,8 @@ Item {
         onNewViewRequested: {
             request.openIn(webview);
         }
+
+        HiFiControls.WebSpinner { }
     }
 
     HiFiControls.Keyboard {
diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml
index 04ff731a25..06766fa6ef 100644
--- a/interface/resources/qml/controls/WebView.qml
+++ b/interface/resources/qml/controls/WebView.qml
@@ -116,6 +116,8 @@ Item {
                 tabletRoot.openBrowserWindow(request, profile);
             }
         }
+
+        HiFiControls.WebSpinner { }
     }
 
     HiFiControls.Keyboard {
diff --git a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml
index 0e98f79216..b6f906ffc2 100644
--- a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml
+++ b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml
@@ -181,10 +181,10 @@ Item {
                 HifiControls.SpinBox {
                     id: scaleSpinner;
                     anchors { left: parent.left; right: parent.right; bottom: parent.bottom; }
-                    decimals: 1;
-                    minimumValue: 0.1
+                    decimals: 2;
+                    minimumValue: 0.01
                     maximumValue: 10
-                    stepSize: 0.1;
+                    stepSize: 0.05;
                     value: attachment ? attachment.scale : 1.0
                     colorScheme: hifi.colorSchemes.dark
                     onValueChanged: {
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 1d31a84ed4..ae490c05e7 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -431,7 +431,7 @@ static const QString STATE_ADVANCED_MOVEMENT_CONTROLS = "AdvancedMovement";
 static const QString STATE_GROUNDED = "Grounded";
 static const QString STATE_NAV_FOCUSED = "NavigationFocused";
 
-bool setupEssentials(int& argc, char** argv) {
+bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
     const char** constArgv = const_cast<const char**>(argv);
     const char* portStr = getCmdOption(argc, constArgv, "--listenPort");
     const int listenPort = portStr ? atoi(portStr) : INVALID_PORT;
@@ -458,7 +458,7 @@ bool setupEssentials(int& argc, char** argv) {
 
     static const auto SUPPRESS_SETTINGS_RESET = "--suppress-settings-reset";
     bool suppressPrompt = cmdOptionExists(argc, const_cast<const char**>(argv), SUPPRESS_SETTINGS_RESET);
-    bool previousSessionCrashed = CrashHandler::checkForResetSettings(suppressPrompt);
+    bool previousSessionCrashed = CrashHandler::checkForResetSettings(runningMarkerExisted, suppressPrompt);
 
     DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
     DependencyManager::registerInheritance<AvatarHashMap, AvatarManager>();
@@ -563,11 +563,11 @@ const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true;
 const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false;
 const bool DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS = false;
 
-Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
+Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bool runningMarkerExisted) :
     QApplication(argc, argv),
     _window(new MainWindow(desktop())),
     _sessionRunTimer(startupTimer),
-    _previousSessionCrashed(setupEssentials(argc, argv)),
+    _previousSessionCrashed(setupEssentials(argc, argv, runningMarkerExisted)),
     _undoStackScriptingInterface(&_undoStack),
     _entitySimulation(new PhysicalEntitySimulation()),
     _physicsEngine(new PhysicsEngine(Vectors::ZERO)),
@@ -3976,7 +3976,7 @@ void Application::updateMyAvatarLookAtPosition() {
             if (faceAngle < MAXIMUM_FACE_ANGLE) {
                 // Randomly look back and forth between look targets
                 eyeContactTarget target = Menu::getInstance()->isOptionChecked(MenuOption::FixGaze) ?
-                LEFT_EYE : myAvatar->getEyeContactTarget();
+                    LEFT_EYE : myAvatar->getEyeContactTarget();
                 switch (target) {
                     case LEFT_EYE:
                         lookAtSpot = lookingAtHead->getLeftEyePosition();
@@ -6428,7 +6428,7 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa
         if (!includeAnimated) {
             // Tell the dependency manager that the capture of the still snapshot has taken place.
             emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(path, notify);
-        } else {
+        } else if (!SnapshotAnimated::isAlreadyTakingSnapshotAnimated()) {
             // Get an animated GIF snapshot and save it
             SnapshotAnimated::saveSnapshotAnimated(path, aspectRatio, qApp, DependencyManager::get<WindowScriptingInterface>());
         }
diff --git a/interface/src/Application.h b/interface/src/Application.h
index 5cf3580c09..d9dc3f389f 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -136,7 +136,7 @@ public:
     static void initPlugins(const QStringList& arguments);
     static void shutdownPlugins();
 
-    Application(int& argc, char** argv, QElapsedTimer& startup_time);
+    Application(int& argc, char** argv, QElapsedTimer& startup_time, bool runningMarkerExisted);
     ~Application();
 
     void postLambdaEvent(std::function<void()> f) override;
diff --git a/interface/src/CrashHandler.cpp b/interface/src/CrashHandler.cpp
index b254cd58de..8081dd3ffc 100644
--- a/interface/src/CrashHandler.cpp
+++ b/interface/src/CrashHandler.cpp
@@ -28,7 +28,7 @@
 
 #include <RunningMarker.h>
 
-bool CrashHandler::checkForResetSettings(bool suppressPrompt) {
+bool CrashHandler::checkForResetSettings(bool wasLikelyCrash, bool suppressPrompt) {
     Settings settings;
     settings.beginGroup("Developer");
     QVariant displayCrashOptions = settings.value(MenuOption::DisplayCrashOptions);
@@ -39,9 +39,6 @@ bool CrashHandler::checkForResetSettings(bool suppressPrompt) {
     // If option does not exist in Interface.ini so assume default behavior.
     bool displaySettingsResetOnCrash = !displayCrashOptions.isValid() || displayCrashOptions.toBool();
 
-    QFile runningMarkerFile(RunningMarker::getMarkerFilePath(RUNNING_MARKER_FILENAME));
-    bool wasLikelyCrash = runningMarkerFile.exists();
-
     if (suppressPrompt) {
         return wasLikelyCrash;
     }
diff --git a/interface/src/CrashHandler.h b/interface/src/CrashHandler.h
index 308cac3411..da2e1575db 100644
--- a/interface/src/CrashHandler.h
+++ b/interface/src/CrashHandler.h
@@ -17,7 +17,7 @@
 class CrashHandler {
 
 public:
-    static bool checkForResetSettings(bool suppressPrompt = false);
+    static bool checkForResetSettings(bool wasLikelyCrash, bool suppressPrompt = false);
 
 private:
     enum Action {
diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index f5f248602d..8461b1d42a 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -525,6 +525,8 @@ Menu::Menu() {
         avatar.get(), SLOT(setEnableDebugDrawIKTargets(bool)));
     addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderIKConstraints, 0, false,
         avatar.get(), SLOT(setEnableDebugDrawIKConstraints(bool)));
+    addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderIKChains, 0, false,
+        avatar.get(), SLOT(setEnableDebugDrawIKChains(bool)));
 
     addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ActionMotorControl,
         Qt::CTRL | Qt::SHIFT | Qt::Key_K, true, avatar.get(), SLOT(updateMotionBehaviorFromMenu()),
diff --git a/interface/src/Menu.h b/interface/src/Menu.h
index b1f69a28d3..b6d72f5446 100644
--- a/interface/src/Menu.h
+++ b/interface/src/Menu.h
@@ -162,6 +162,7 @@ namespace MenuOption {
     const QString RenderSensorToWorldMatrix = "Show SensorToWorld Matrix";
     const QString RenderIKTargets = "Show IK Targets";
     const QString RenderIKConstraints = "Show IK Constraints";
+    const QString RenderIKChains = "Show IK Chains";
     const QString ResetAvatarSize = "Reset Avatar Size";
     const QString ResetSensors = "Reset Sensors";
     const QString RunningScripts = "Running Scripts...";
diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp
index d47e4cfd10..20b3949bc6 100644
--- a/interface/src/avatar/AvatarManager.cpp
+++ b/interface/src/avatar/AvatarManager.cpp
@@ -52,7 +52,7 @@ const QUuid MY_AVATAR_KEY;  // NULL key
 
 AvatarManager::AvatarManager(QObject* parent) :
     _avatarsToFade(),
-    _myAvatar(std::make_shared<MyAvatar>(qApp->thread(), std::make_shared<Rig>()))
+    _myAvatar(std::make_shared<MyAvatar>(qApp->thread()))
 {
     // register a meta type for the weak pointer we'll use for the owning avatar mixer for each avatar
     qRegisterMetaType<QWeakPointer<Node> >("NodeWeakPointer");
@@ -300,7 +300,7 @@ void AvatarManager::simulateAvatarFades(float deltaTime) {
 }
 
 AvatarSharedPointer AvatarManager::newSharedAvatar() {
-    return std::make_shared<OtherAvatar>(qApp->thread(), std::make_shared<Rig>());
+    return std::make_shared<OtherAvatar>(qApp->thread());
 }
 
 void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index 9cf8e7747b..b4e418b6a2 100755
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -98,8 +98,8 @@ static const glm::quat DEFAULT_AVATAR_LEFTFOOT_ROT { -0.40167322754859924f, 0.91
 static const glm::vec3 DEFAULT_AVATAR_RIGHTFOOT_POS { 0.08f, -0.96f, 0.029f };
 static const glm::quat DEFAULT_AVATAR_RIGHTFOOT_ROT { -0.4016716778278351f, 0.9154615998268127f, 0.0053307069465518f, 0.023696165531873703f };
 
-MyAvatar::MyAvatar(QThread* thread, RigPointer rig) :
-    Avatar(thread, rig),
+MyAvatar::MyAvatar(QThread* thread) :
+    Avatar(thread),
     _yawSpeed(YAW_SPEED_DEFAULT),
     _pitchSpeed(PITCH_SPEED_DEFAULT),
     _scriptedMotorTimescale(DEFAULT_SCRIPTED_MOTOR_TIMESCALE),
@@ -120,7 +120,6 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) :
     _goToPending(false),
     _goToPosition(),
     _goToOrientation(),
-    _rig(rig),
     _prevShouldDrawHead(true),
     _audioListenerMode(FROM_HEAD),
     _hmdAtRestDetector(glm::vec3(0), glm::quat())
@@ -129,7 +128,7 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) :
     // give the pointer to our head to inherited _headData variable from AvatarData
     _headData = new MyHead(this);
 
-    _skeletonModel = std::make_shared<MySkeletonModel>(this, nullptr, rig);
+    _skeletonModel = std::make_shared<MySkeletonModel>(this, nullptr);
     connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished);
 
 
@@ -180,9 +179,7 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) :
         auto audioIO = DependencyManager::get<AudioClient>();
         audioIO->setIsPlayingBackRecording(isPlaying);
 
-        if (_rig) {
-            _rig->setEnableAnimations(!isPlaying);
-        }
+        _skeletonModel->getRig().setEnableAnimations(!isPlaying);
     });
 
     connect(recorder.data(), &Recorder::recordingStateChanged, [=] {
@@ -233,12 +230,12 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) :
         }
 
         auto jointData = dummyAvatar.getRawJointData();
-        if (jointData.length() > 0 && _rig) {
-            _rig->copyJointsFromJointData(jointData);
+        if (jointData.length() > 0) {
+            _skeletonModel->getRig().copyJointsFromJointData(jointData);
         }
     });
 
-    connect(rig.get(), SIGNAL(onLoadComplete()), this, SIGNAL(onLoadComplete()));
+    connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SIGNAL(onLoadComplete()));
     _characterController.setDensity(_density);
 }
 
@@ -355,9 +352,7 @@ void MyAvatar::clearIKJointLimitHistory() {
         return;
     }
 
-    if (_rig) {
-        _rig->clearIKJointLimitHistory();
-    }
+    _skeletonModel->getRig().clearIKJointLimitHistory();
 }
 
 void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) {
@@ -528,11 +523,9 @@ void MyAvatar::simulate(float deltaTime) {
     {
         PerformanceTimer perfTimer("skeleton");
 
-        if (_rig) {
-            _rig->setEnableDebugDrawIKTargets(_enableDebugDrawIKTargets);
-            _rig->setEnableDebugDrawIKConstraints(_enableDebugDrawIKConstraints);
-        }
-
+        _skeletonModel->getRig().setEnableDebugDrawIKTargets(_enableDebugDrawIKTargets);
+        _skeletonModel->getRig().setEnableDebugDrawIKConstraints(_enableDebugDrawIKConstraints);
+        _skeletonModel->getRig().setEnableDebugDrawIKChains(_enableDebugDrawIKChains);
         _skeletonModel->simulate(deltaTime);
     }
 
@@ -550,7 +543,7 @@ void MyAvatar::simulate(float deltaTime) {
         PerformanceTimer perfTimer("joints");
         // copy out the skeleton joints from the model
         if (_rigEnabled) {
-            _rig->copyJointsIntoJointData(_jointData);
+            _skeletonModel->getRig().copyJointsIntoJointData(_jointData);
         }
     }
 
@@ -795,7 +788,7 @@ void MyAvatar::overrideAnimation(const QString& url, float fps, bool loop, float
                                   Q_ARG(bool, loop), Q_ARG(float, firstFrame), Q_ARG(float, lastFrame));
         return;
     }
-    _rig->overrideAnimation(url, fps, loop, firstFrame, lastFrame);
+    _skeletonModel->getRig().overrideAnimation(url, fps, loop, firstFrame, lastFrame);
 }
 
 void MyAvatar::restoreAnimation() {
@@ -803,7 +796,7 @@ void MyAvatar::restoreAnimation() {
         QMetaObject::invokeMethod(this, "restoreAnimation");
         return;
     }
-    _rig->restoreAnimation();
+    _skeletonModel->getRig().restoreAnimation();
 }
 
 QStringList MyAvatar::getAnimationRoles() {
@@ -812,7 +805,7 @@ QStringList MyAvatar::getAnimationRoles() {
         QMetaObject::invokeMethod(this, "getAnimationRoles", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QStringList, result));
         return result;
     }
-    return _rig->getAnimationRoles();
+    return _skeletonModel->getRig().getAnimationRoles();
 }
 
 void MyAvatar::overrideRoleAnimation(const QString& role, const QString& url, float fps, bool loop,
@@ -822,7 +815,7 @@ void MyAvatar::overrideRoleAnimation(const QString& role, const QString& url, fl
                                   Q_ARG(float, fps), Q_ARG(bool, loop), Q_ARG(float, firstFrame), Q_ARG(float, lastFrame));
         return;
     }
-    _rig->overrideRoleAnimation(role, url, fps, loop, firstFrame, lastFrame);
+    _skeletonModel->getRig().overrideRoleAnimation(role, url, fps, loop, firstFrame, lastFrame);
 }
 
 void MyAvatar::restoreRoleAnimation(const QString& role) {
@@ -830,7 +823,7 @@ void MyAvatar::restoreRoleAnimation(const QString& role) {
         QMetaObject::invokeMethod(this, "restoreRoleAnimation", Q_ARG(const QString&, role));
         return;
     }
-    _rig->restoreRoleAnimation(role);
+    _skeletonModel->getRig().restoreRoleAnimation(role);
 }
 
 void MyAvatar::saveData() {
@@ -958,6 +951,10 @@ void MyAvatar::setEnableDebugDrawIKConstraints(bool isEnabled) {
     _enableDebugDrawIKConstraints = isEnabled;
 }
 
+void MyAvatar::setEnableDebugDrawIKChains(bool isEnabled) {
+    _enableDebugDrawIKChains = isEnabled;
+}
+
 void MyAvatar::setEnableMeshVisible(bool isEnabled) {
     _skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene());
 }
@@ -968,7 +965,7 @@ void MyAvatar::setUseAnimPreAndPostRotations(bool isEnabled) {
 }
 
 void MyAvatar::setEnableInverseKinematics(bool isEnabled) {
-    _rig->setEnableInverseKinematics(isEnabled);
+    _skeletonModel->getRig().setEnableInverseKinematics(isEnabled);
 }
 
 void MyAvatar::loadData() {
@@ -1217,7 +1214,7 @@ void MyAvatar::setJointData(int index, const glm::quat& rotation, const glm::vec
         return;
     }
     // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority
-    _rig->setJointState(index, true, rotation, translation, SCRIPT_PRIORITY);
+    _skeletonModel->getRig().setJointState(index, true, rotation, translation, SCRIPT_PRIORITY);
 }
 
 void MyAvatar::setJointRotation(int index, const glm::quat& rotation) {
@@ -1226,7 +1223,7 @@ void MyAvatar::setJointRotation(int index, const glm::quat& rotation) {
         return;
     }
     // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority
-    _rig->setJointRotation(index, true, rotation, SCRIPT_PRIORITY);
+    _skeletonModel->getRig().setJointRotation(index, true, rotation, SCRIPT_PRIORITY);
 }
 
 void MyAvatar::setJointTranslation(int index, const glm::vec3& translation) {
@@ -1235,7 +1232,7 @@ void MyAvatar::setJointTranslation(int index, const glm::vec3& translation) {
         return;
     }
     // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority
-    _rig->setJointTranslation(index, true, translation, SCRIPT_PRIORITY);
+    _skeletonModel->getRig().setJointTranslation(index, true, translation, SCRIPT_PRIORITY);
 }
 
 void MyAvatar::clearJointData(int index) {
@@ -1243,7 +1240,7 @@ void MyAvatar::clearJointData(int index) {
         QMetaObject::invokeMethod(this, "clearJointData", Q_ARG(int, index));
         return;
     }
-    _rig->clearJointAnimationPriority(index);
+    _skeletonModel->getRig().clearJointAnimationPriority(index);
 }
 
 void MyAvatar::clearJointsData() {
@@ -1251,7 +1248,7 @@ void MyAvatar::clearJointsData() {
         QMetaObject::invokeMethod(this, "clearJointsData");
         return;
     }
-    _rig->clearJointStates();
+    _skeletonModel->getRig().clearJointStates();
 }
 
 void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
@@ -1694,7 +1691,7 @@ void MyAvatar::setAnimGraphUrl(const QUrl& url) {
     _skeletonModel->reset(); // Why is this necessary? Without this, we crash in the next render.
 
     _currentAnimGraphUrl.set(url);
-    _rig->initAnimGraph(url);
+    _skeletonModel->getRig().initAnimGraph(url);
 
     _bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation..
     updateSensorToWorldMatrix(); // Uses updated position/orientation and _bodySensorMatrix changes
@@ -1710,7 +1707,7 @@ void MyAvatar::initAnimGraph() {
         graphUrl = QUrl::fromLocalFile(PathUtils::resourcesPath() + "avatar/avatar-animation.json");
     }
 
-    _rig->initAnimGraph(graphUrl);
+    _skeletonModel->getRig().initAnimGraph(graphUrl);
     _currentAnimGraphUrl.set(graphUrl);
 
     _bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation..
@@ -1718,7 +1715,7 @@ void MyAvatar::initAnimGraph() {
 }
 
 void MyAvatar::destroyAnimGraph() {
-    _rig->destroyAnimGraph();
+    _skeletonModel->getRig().destroyAnimGraph();
 }
 
 void MyAvatar::postUpdate(float deltaTime) {
@@ -1734,22 +1731,23 @@ void MyAvatar::postUpdate(float deltaTime) {
 
     if (_enableDebugDrawDefaultPose || _enableDebugDrawAnimPose) {
 
-        auto animSkeleton = _rig->getAnimSkeleton();
+        auto animSkeleton = _skeletonModel->getRig().getAnimSkeleton();
 
         // the rig is in the skeletonModel frame
         AnimPose xform(glm::vec3(1), _skeletonModel->getRotation(), _skeletonModel->getTranslation());
 
         if (_enableDebugDrawDefaultPose && animSkeleton) {
             glm::vec4 gray(0.2f, 0.2f, 0.2f, 0.2f);
-            AnimDebugDraw::getInstance().addAbsolutePoses("myAvatarDefaultPoses", animSkeleton, _rig->getAbsoluteDefaultPoses(), xform, gray);
+            AnimDebugDraw::getInstance().addAbsolutePoses("myAvatarDefaultPoses", animSkeleton, _skeletonModel->getRig().getAbsoluteDefaultPoses(), xform, gray);
         }
 
         if (_enableDebugDrawAnimPose && animSkeleton) {
             // build absolute AnimPoseVec from rig
             AnimPoseVec absPoses;
-            absPoses.reserve(_rig->getJointStateCount());
-            for (int i = 0; i < _rig->getJointStateCount(); i++) {
-                absPoses.push_back(AnimPose(_rig->getJointTransform(i)));
+            const Rig& rig = _skeletonModel->getRig();
+            absPoses.reserve(rig.getJointStateCount());
+            for (int i = 0; i < rig.getJointStateCount(); i++) {
+                absPoses.push_back(AnimPose(rig.getJointTransform(i)));
             }
             glm::vec4 cyan(0.1f, 0.6f, 0.6f, 1.0f);
             AnimDebugDraw::getInstance().addAbsolutePoses("myAvatarAnimPoses", animSkeleton, absPoses, xform, cyan);
@@ -2338,17 +2336,18 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const {
     const glm::quat hmdOrientation = getHMDSensorOrientation();
     const glm::quat hmdOrientationYawOnly = cancelOutRollAndPitch(hmdOrientation);
 
-    int rightEyeIndex = _rig->indexOfJoint("RightEye");
-    int leftEyeIndex = _rig->indexOfJoint("LeftEye");
-    int neckIndex = _rig->indexOfJoint("Neck");
-    int hipsIndex = _rig->indexOfJoint("Hips");
+    const Rig& rig = _skeletonModel->getRig();
+    int rightEyeIndex = rig.indexOfJoint("RightEye");
+    int leftEyeIndex = rig.indexOfJoint("LeftEye");
+    int neckIndex = rig.indexOfJoint("Neck");
+    int hipsIndex = rig.indexOfJoint("Hips");
 
     glm::vec3 rigMiddleEyePos = DEFAULT_AVATAR_MIDDLE_EYE_POS;
     if (leftEyeIndex >= 0 && rightEyeIndex >= 0) {
-        rigMiddleEyePos = (_rig->getAbsoluteDefaultPose(leftEyeIndex).trans() + _rig->getAbsoluteDefaultPose(rightEyeIndex).trans()) / 2.0f;
+        rigMiddleEyePos =  (rig.getAbsoluteDefaultPose(leftEyeIndex).trans() + rig.getAbsoluteDefaultPose(rightEyeIndex).trans()) / 2.0f;
     }
-    glm::vec3 rigNeckPos = neckIndex != -1 ? _rig->getAbsoluteDefaultPose(neckIndex).trans() : DEFAULT_AVATAR_NECK_POS;
-    glm::vec3 rigHipsPos = hipsIndex != -1 ? _rig->getAbsoluteDefaultPose(hipsIndex).trans() : DEFAULT_AVATAR_HIPS_POS;
+    glm::vec3 rigNeckPos = neckIndex != -1 ? rig.getAbsoluteDefaultPose(neckIndex).trans() : DEFAULT_AVATAR_NECK_POS;
+    glm::vec3 rigHipsPos = hipsIndex != -1 ? rig.getAbsoluteDefaultPose(hipsIndex).trans() : DEFAULT_AVATAR_HIPS_POS;
 
     glm::vec3 localEyes = (rigMiddleEyePos - rigHipsPos);
     glm::vec3 localNeck = (rigNeckPos - rigHipsPos);
@@ -2714,8 +2713,8 @@ glm::vec3 MyAvatar::getAbsoluteJointTranslationInObjectFrame(int index) const {
 
 glm::mat4 MyAvatar::getCenterEyeCalibrationMat() const {
     // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed.
-    int rightEyeIndex = _rig->indexOfJoint("RightEye");
-    int leftEyeIndex = _rig->indexOfJoint("LeftEye");
+    int rightEyeIndex = _skeletonModel->getRig().indexOfJoint("RightEye");
+    int leftEyeIndex = _skeletonModel->getRig().indexOfJoint("LeftEye");
     if (rightEyeIndex >= 0 && leftEyeIndex >= 0) {
         auto centerEyePos = (getAbsoluteDefaultJointTranslationInObjectFrame(rightEyeIndex) + getAbsoluteDefaultJointTranslationInObjectFrame(leftEyeIndex)) * 0.5f;
         auto centerEyeRot = Quaternions::Y_180;
@@ -2727,7 +2726,7 @@ glm::mat4 MyAvatar::getCenterEyeCalibrationMat() const {
 
 glm::mat4 MyAvatar::getHeadCalibrationMat() const {
     // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed.
-    int headIndex = _rig->indexOfJoint("Head");
+    int headIndex = _skeletonModel->getRig().indexOfJoint("Head");
     if (headIndex >= 0) {
         auto headPos = getAbsoluteDefaultJointTranslationInObjectFrame(headIndex);
         auto headRot = getAbsoluteDefaultJointRotationInObjectFrame(headIndex);
@@ -2739,7 +2738,7 @@ glm::mat4 MyAvatar::getHeadCalibrationMat() const {
 
 glm::mat4 MyAvatar::getSpine2CalibrationMat() const {
     // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed.
-    int spine2Index = _rig->indexOfJoint("Spine2");
+    int spine2Index = _skeletonModel->getRig().indexOfJoint("Spine2");
     if (spine2Index >= 0) {
         auto spine2Pos = getAbsoluteDefaultJointTranslationInObjectFrame(spine2Index);
         auto spine2Rot = getAbsoluteDefaultJointRotationInObjectFrame(spine2Index);
@@ -2751,7 +2750,7 @@ glm::mat4 MyAvatar::getSpine2CalibrationMat() const {
 
 glm::mat4 MyAvatar::getHipsCalibrationMat() const {
     // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed.
-    int hipsIndex = _rig->indexOfJoint("Hips");
+    int hipsIndex = _skeletonModel->getRig().indexOfJoint("Hips");
     if (hipsIndex >= 0) {
         auto hipsPos = getAbsoluteDefaultJointTranslationInObjectFrame(hipsIndex);
         auto hipsRot = getAbsoluteDefaultJointRotationInObjectFrame(hipsIndex);
@@ -2763,7 +2762,7 @@ glm::mat4 MyAvatar::getHipsCalibrationMat() const {
 
 glm::mat4 MyAvatar::getLeftFootCalibrationMat() const {
     // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed.
-    int leftFootIndex = _rig->indexOfJoint("LeftFoot");
+    int leftFootIndex = _skeletonModel->getRig().indexOfJoint("LeftFoot");
     if (leftFootIndex >= 0) {
         auto leftFootPos = getAbsoluteDefaultJointTranslationInObjectFrame(leftFootIndex);
         auto leftFootRot = getAbsoluteDefaultJointRotationInObjectFrame(leftFootIndex);
@@ -2775,7 +2774,7 @@ glm::mat4 MyAvatar::getLeftFootCalibrationMat() const {
 
 glm::mat4 MyAvatar::getRightFootCalibrationMat() const {
     // TODO: as an optimization cache this computation, then invalidate the cache when the avatar model is changed.
-    int rightFootIndex = _rig->indexOfJoint("RightFoot");
+    int rightFootIndex = _skeletonModel->getRig().indexOfJoint("RightFoot");
     if (rightFootIndex >= 0) {
         auto rightFootPos = getAbsoluteDefaultJointTranslationInObjectFrame(rightFootIndex);
         auto rightFootRot = getAbsoluteDefaultJointRotationInObjectFrame(rightFootIndex);
@@ -2795,7 +2794,7 @@ bool MyAvatar::pinJoint(int index, const glm::vec3& position, const glm::quat& o
     slamPosition(position);
     setOrientation(orientation);
 
-    _rig->setMaxHipsOffsetLength(0.05f);
+    _skeletonModel->getRig().setMaxHipsOffsetLength(0.05f);
 
     auto it = std::find(_pinnedJoints.begin(), _pinnedJoints.end(), index);
     if (it == _pinnedJoints.end()) {
@@ -2812,7 +2811,7 @@ bool MyAvatar::clearPinOnJoint(int index) {
 
         auto hipsIndex = getJointIndex("Hips");
         if (index == hipsIndex) {
-            _rig->setMaxHipsOffsetLength(FLT_MAX);
+            _skeletonModel->getRig().setMaxHipsOffsetLength(FLT_MAX);
         }
 
         return true;
@@ -2821,7 +2820,7 @@ bool MyAvatar::clearPinOnJoint(int index) {
 }
 
 float MyAvatar::getIKErrorOnLastSolve() const {
-    return _rig->getIKErrorOnLastSolve();
+    return _skeletonModel->getRig().getIKErrorOnLastSolve();
 }
 
 // thread-safe
diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h
index 394d6b2ac7..fde350a43e 100644
--- a/interface/src/avatar/MyAvatar.h
+++ b/interface/src/avatar/MyAvatar.h
@@ -148,7 +148,7 @@ public:
     };
     Q_ENUM(DriveKeys)
 
-    explicit MyAvatar(QThread* thread, RigPointer rig);
+    explicit MyAvatar(QThread* thread);
     ~MyAvatar();
 
     void instantiableAvatar() override {};
@@ -322,9 +322,9 @@ public:
     // adding one of the other handlers. While any handler may change a value in animStateDictionaryIn (or supply different values in animStateDictionaryOut)
     // a handler must not remove properties from animStateDictionaryIn, nor change property values that it does not intend to change.
     // It is not specified in what order multiple handlers are called.
-    Q_INVOKABLE QScriptValue addAnimationStateHandler(QScriptValue handler, QScriptValue propertiesList) { return _rig->addAnimationStateHandler(handler, propertiesList); }
+    Q_INVOKABLE QScriptValue addAnimationStateHandler(QScriptValue handler, QScriptValue propertiesList) { return _skeletonModel->getRig().addAnimationStateHandler(handler, propertiesList); }
     // Removes a handler previously added by addAnimationStateHandler.
-    Q_INVOKABLE void removeAnimationStateHandler(QScriptValue handler) { _rig->removeAnimationStateHandler(handler); }
+    Q_INVOKABLE void removeAnimationStateHandler(QScriptValue handler) { _skeletonModel->getRig().removeAnimationStateHandler(handler); }
 
     Q_INVOKABLE bool getSnapTurn() const { return _useSnapTurn; }
     Q_INVOKABLE void setSnapTurn(bool on) { _useSnapTurn = on; }
@@ -524,6 +524,7 @@ public slots:
     void setEnableDebugDrawSensorToWorldMatrix(bool isEnabled);
     void setEnableDebugDrawIKTargets(bool isEnabled);
     void setEnableDebugDrawIKConstraints(bool isEnabled);
+    void setEnableDebugDrawIKChains(bool isEnabled);
     bool getEnableMeshVisible() const { return _skeletonModel->isVisible(); }
     void setEnableMeshVisible(bool isEnabled);
     void setUseAnimPreAndPostRotations(bool isEnabled);
@@ -708,7 +709,6 @@ private:
     glm::quat _goToOrientation;
 
     std::unordered_set<int> _headBoneSet;
-    RigPointer _rig;
     bool _prevShouldDrawHead;
     bool _rigEnabled { true };
 
@@ -718,6 +718,7 @@ private:
     bool _enableDebugDrawSensorToWorldMatrix { false };
     bool _enableDebugDrawIKTargets { false };
     bool _enableDebugDrawIKConstraints { false };
+    bool _enableDebugDrawIKChains { false };
 
     AudioListenerMode _audioListenerMode;
     glm::vec3 _customListenPosition;
diff --git a/interface/src/avatar/MyHead.cpp b/interface/src/avatar/MyHead.cpp
index 793fbb79c4..f02aefec5b 100644
--- a/interface/src/avatar/MyHead.cpp
+++ b/interface/src/avatar/MyHead.cpp
@@ -44,17 +44,14 @@ glm::quat MyHead::getCameraOrientation() const {
 void MyHead::simulate(float deltaTime) {
     auto player = DependencyManager::get<recording::Deck>();
     // Only use face trackers when not playing back a recording.
-    if (player->isPlaying()) {
-        Parent::simulate(deltaTime);
-    } else {
-        computeAudioLoudness(deltaTime);
-
+    if (!player->isPlaying()) {
         FaceTracker* faceTracker = qApp->getActiveFaceTracker();
-        _isFaceTrackerConnected = faceTracker && !faceTracker->isMuted();
+        _isFaceTrackerConnected = faceTracker != nullptr && !faceTracker->isMuted();
         if (_isFaceTrackerConnected) {
             _transientBlendshapeCoefficients = faceTracker->getBlendshapeCoefficients();
 
             if (typeid(*faceTracker) == typeid(DdeFaceTracker)) {
+
                 if (Menu::getInstance()->isOptionChecked(MenuOption::UseAudioForMouth)) {
                     calculateMouthShapes(deltaTime);
 
@@ -71,19 +68,9 @@ void MyHead::simulate(float deltaTime) {
                 }
                 applyEyelidOffset(getFinalOrientationInWorldFrame());
             }
-        } else {
-            computeFaceMovement(deltaTime);
-		}
-
-        auto eyeTracker = DependencyManager::get<EyeTracker>();
-        _isEyeTrackerConnected = eyeTracker && eyeTracker->isTracking();
-        if (_isEyeTrackerConnected) {
-            // TODO? figure out where EyeTracker data harvested. Move it here?
-            _saccade = glm::vec3();
-        } else {
-            computeEyeMovement(deltaTime);
         }
-
+        auto eyeTracker = DependencyManager::get<EyeTracker>();
+        _isEyeTrackerConnected = eyeTracker->isTracking();
     }
-    computeEyePosition();
+    Parent::simulate(deltaTime);
 }
diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp
index e60481fc62..828a5f8a01 100644
--- a/interface/src/avatar/MySkeletonModel.cpp
+++ b/interface/src/avatar/MySkeletonModel.cpp
@@ -13,7 +13,7 @@
 #include "Application.h"
 #include "InterfaceLogging.h"
 
-MySkeletonModel::MySkeletonModel(Avatar* owningAvatar, QObject* parent, RigPointer rig) : SkeletonModel(owningAvatar, parent, rig) {
+MySkeletonModel::MySkeletonModel(Avatar* owningAvatar, QObject* parent) : SkeletonModel(owningAvatar, parent) {
 }
 
 Rig::CharacterControllerState convertCharacterControllerState(CharacterController::State state) {
@@ -63,7 +63,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
             glm::mat4 rigToWorld = createMatFromQuatAndPos(getRotation(), getTranslation());
             glm::mat4 worldToRig = glm::inverse(rigToWorld);
             glm::mat4 rigHMDMat = worldToRig * worldHMDMat;
-            _rig->computeHeadFromHMD(AnimPose(rigHMDMat), headParams.rigHeadPosition, headParams.rigHeadOrientation);
+            _rig.computeHeadFromHMD(AnimPose(rigHMDMat), headParams.rigHeadPosition, headParams.rigHeadOrientation);
             headParams.headEnabled = true;
         } else {
             // even though full head IK is disabled, the rig still needs the head orientation to rotate the head up and down in desktop mode.
@@ -94,7 +94,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
 
     headParams.isTalking = head->getTimeWithoutTalking() <= 1.5f;
 
-    _rig->updateFromHeadParameters(headParams, deltaTime);
+    _rig.updateFromHeadParameters(headParams, deltaTime);
 
     Rig::HandAndFeetParameters handAndFeetParams;
 
@@ -138,14 +138,14 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
     handAndFeetParams.bodyCapsuleHalfHeight = myAvatar->getCharacterController()->getCapsuleHalfHeight();
     handAndFeetParams.bodyCapsuleLocalOffset = myAvatar->getCharacterController()->getCapsuleLocalOffset();
 
-    _rig->updateFromHandAndFeetParameters(handAndFeetParams, deltaTime);
+    _rig.updateFromHandAndFeetParameters(handAndFeetParams, deltaTime);
 
     Rig::CharacterControllerState ccState = convertCharacterControllerState(myAvatar->getCharacterController()->getState());
 
     auto velocity = myAvatar->getLocalVelocity();
     auto position = myAvatar->getLocalPosition();
     auto orientation = myAvatar->getLocalOrientation();
-    _rig->computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState);
+    _rig.computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState);
 
     // evaluate AnimGraph animation and update jointStates.
     Model::updateRig(deltaTime, parentTransform);
@@ -158,6 +158,6 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
     eyeParams.leftEyeJointIndex = geometry.leftEyeJointIndex;
     eyeParams.rightEyeJointIndex = geometry.rightEyeJointIndex;
 
-    _rig->updateFromEyeParameters(eyeParams);
+    _rig.updateFromEyeParameters(eyeParams);
 }
 
diff --git a/interface/src/avatar/MySkeletonModel.h b/interface/src/avatar/MySkeletonModel.h
index 84fccc825a..12aba6b545 100644
--- a/interface/src/avatar/MySkeletonModel.h
+++ b/interface/src/avatar/MySkeletonModel.h
@@ -19,7 +19,7 @@ private:
     using Parent = SkeletonModel;
 
 public:
-    MySkeletonModel(Avatar* owningAvatar, QObject* parent = nullptr, RigPointer rig = nullptr);
+    MySkeletonModel(Avatar* owningAvatar, QObject* parent = nullptr);
     void updateRig(float deltaTime, glm::mat4 parentTransform) override;
 };
 
diff --git a/interface/src/main.cpp b/interface/src/main.cpp
index 3430ffbd15..49517eb38e 100644
--- a/interface/src/main.cpp
+++ b/interface/src/main.cpp
@@ -192,6 +192,7 @@ int main(int argc, const char* argv[]) {
     int exitCode;
     {
         RunningMarker runningMarker(nullptr, RUNNING_MARKER_FILENAME);
+        bool runningMarkerExisted = runningMarker.fileExists();
         runningMarker.writeRunningMarkerFile();
 
         bool noUpdater = parser.isSet(noUpdaterOption);
@@ -202,7 +203,7 @@ int main(int argc, const char* argv[]) {
             SandboxUtils::runLocalSandbox(serverContentPath, true, RUNNING_MARKER_FILENAME, noUpdater);
         }
 
-        Application app(argc, const_cast<char**>(argv), startupTime);
+        Application app(argc, const_cast<char**>(argv), startupTime, runningMarkerExisted);
 
         // Now that the main event loop is setup, launch running marker thread
         runningMarker.startRunningMarker();
diff --git a/interface/src/ui/SnapshotAnimated.h b/interface/src/ui/SnapshotAnimated.h
index 1cf21edfb8..dd32e4893d 100644
--- a/interface/src/ui/SnapshotAnimated.h
+++ b/interface/src/ui/SnapshotAnimated.h
@@ -51,6 +51,7 @@ private:
     static void processFrames();
 public:
     static void saveSnapshotAnimated(QString pathStill, float aspectRatio, Application* app, QSharedPointer<WindowScriptingInterface> dm);
+    static bool isAlreadyTakingSnapshotAnimated() { return snapshotAnimatedFirstFrameTimestamp != 0; };
     static Setting::Handle<bool> alsoTakeAnimatedSnapshot;
     static Setting::Handle<float> snapshotAnimatedDuration;
 };
diff --git a/interface/src/ui/UpdateDialog.cpp b/interface/src/ui/UpdateDialog.cpp
index 60acd0895b..2dcc0c07eb 100644
--- a/interface/src/ui/UpdateDialog.cpp
+++ b/interface/src/ui/UpdateDialog.cpp
@@ -23,11 +23,8 @@ UpdateDialog::UpdateDialog(QQuickItem* parent) :
     auto applicationUpdater = DependencyManager::get<AutoUpdater>();
     int currentVersion = QCoreApplication::applicationVersion().toInt();
     int latestVersion = applicationUpdater.data()->getBuildData().lastKey();
-    int versionsBehind = latestVersion - currentVersion;
     _updateAvailableDetails = "v" + QString::number(latestVersion) + " released on "
         + QString(applicationUpdater.data()->getBuildData()[latestVersion]["releaseTime"]).replace("  ", " ");
-    _updateAvailableDetails += "\nYou are " + QString::number(versionsBehind) + " version" 
-        + (versionsBehind > 1 ? "s" : "") + " behind";
 
     _releaseNotes = "";
     for (int i = latestVersion; i > currentVersion; i--) {
diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp
index 307f23bff3..0bed07891e 100644
--- a/interface/src/ui/overlays/ModelOverlay.cpp
+++ b/interface/src/ui/overlays/ModelOverlay.cpp
@@ -18,7 +18,7 @@
 QString const ModelOverlay::TYPE = "model";
 
 ModelOverlay::ModelOverlay()
-    : _model(std::make_shared<Model>(std::make_shared<Rig>(), nullptr, this)),
+    : _model(std::make_shared<Model>(nullptr, this)),
       _modelTextures(QVariantMap())
 {
     _model->init();
@@ -28,7 +28,7 @@ ModelOverlay::ModelOverlay()
 
 ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) :
     Volume3DOverlay(modelOverlay),
-    _model(std::make_shared<Model>(std::make_shared<Rig>(), nullptr, this)),
+    _model(std::make_shared<Model>(nullptr, this)),
     _modelTextures(QVariantMap()),
     _url(modelOverlay->_url),
     _updateModel(false),
@@ -211,12 +211,10 @@ QVariant ModelOverlay::getProperty(const QString& property) {
     if (property == "jointNames") {
         if (_model && _model->isActive()) {
             // note: going through Rig because Model::getJointNames() (which proxies to FBXGeometry) was always empty
-            const RigPointer rig = _model->getRig();
-            if (rig) {
-                return mapJoints<QStringList, QString>([rig](int jointIndex) -> QString {
-                    return rig->nameOfJoint(jointIndex);
-                });
-            }
+            const Rig* rig = &(_model->getRig());
+            return mapJoints<QStringList, QString>([rig](int jointIndex) -> QString {
+                return rig->nameOfJoint(jointIndex);
+            });
         }
     }
 
diff --git a/libraries/animation/src/AnimContext.cpp b/libraries/animation/src/AnimContext.cpp
index 70ca3764b0..c8efd83318 100644
--- a/libraries/animation/src/AnimContext.cpp
+++ b/libraries/animation/src/AnimContext.cpp
@@ -10,10 +10,11 @@
 
 #include "AnimContext.h"
 
-AnimContext::AnimContext(bool enableDebugDrawIKTargets, bool enableDebugDrawIKConstraints,
+AnimContext::AnimContext(bool enableDebugDrawIKTargets, bool enableDebugDrawIKConstraints, bool enableDebugDrawIKChains,
                          const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix) :
     _enableDebugDrawIKTargets(enableDebugDrawIKTargets),
     _enableDebugDrawIKConstraints(enableDebugDrawIKConstraints),
+    _enableDebugDrawIKChains(enableDebugDrawIKChains),
     _geometryToRigMatrix(geometryToRigMatrix),
     _rigToWorldMatrix(rigToWorldMatrix)
 {
diff --git a/libraries/animation/src/AnimContext.h b/libraries/animation/src/AnimContext.h
index f68535005c..e8bf5c34eb 100644
--- a/libraries/animation/src/AnimContext.h
+++ b/libraries/animation/src/AnimContext.h
@@ -16,18 +16,20 @@
 
 class AnimContext {
 public:
-    AnimContext(bool enableDebugDrawIKTargets, bool enableDebugDrawIKConstraints,
+    AnimContext(bool enableDebugDrawIKTargets, bool enableDebugDrawIKConstraints, bool enableDebugDrawIKChains,
                 const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix);
 
     bool getEnableDebugDrawIKTargets() const { return _enableDebugDrawIKTargets; }
     bool getEnableDebugDrawIKConstraints() const { return _enableDebugDrawIKConstraints; }
+    bool getEnableDebugDrawIKChains() const { return _enableDebugDrawIKChains; }
     const glm::mat4& getGeometryToRigMatrix() const { return _geometryToRigMatrix; }
     const glm::mat4& getRigToWorldMatrix() const { return _rigToWorldMatrix; }
 
 protected:
 
     bool _enableDebugDrawIKTargets { false };
-    bool _enableDebugDrawIKConstraints{ false };
+    bool _enableDebugDrawIKConstraints { false };
+    bool _enableDebugDrawIKChains { false };
     glm::mat4 _geometryToRigMatrix;
     glm::mat4 _rigToWorldMatrix;
 };
diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp
index 4471f11857..d613e42866 100644
--- a/libraries/animation/src/AnimInverseKinematics.cpp
+++ b/libraries/animation/src/AnimInverseKinematics.cpp
@@ -175,7 +175,7 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::
     }
 }
 
-void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<IKTarget>& targets) {
+void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const AnimContext& context, const std::vector<IKTarget>& targets) {
     // compute absolute poses that correspond to relative target poses
     AnimPoseVec absolutePoses;
     absolutePoses.resize(_relativePoses.size());
@@ -193,25 +193,23 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I
     while (maxError > MAX_ERROR_TOLERANCE && numLoops < MAX_IK_LOOPS) {
         ++numLoops;
 
+        bool debug = context.getEnableDebugDrawIKChains() && numLoops == MAX_IK_LOOPS;
+
         // solve all targets
-        int lowestMovedIndex = (int)_relativePoses.size();
         for (auto& target: targets) {
-            int lowIndex = solveTargetWithCCD(target, absolutePoses);
-            if (lowIndex < lowestMovedIndex) {
-                lowestMovedIndex = lowIndex;
-            }
+            solveTargetWithCCD(context, target, absolutePoses, debug);
         }
 
         // harvest accumulated rotations and apply the average
-        for (int i = lowestMovedIndex; i < _maxTargetIndex; ++i) {
+        for (int i = 0; i < (int)_relativePoses.size(); ++i) {
             if (_accumulators[i].size() > 0) {
                 _relativePoses[i].rot() = _accumulators[i].getAverage();
                 _accumulators[i].clear();
             }
         }
 
-        // update the absolutePoses that need it (from lowestMovedIndex to _maxTargetIndex)
-        for (auto i = lowestMovedIndex; i <= _maxTargetIndex; ++i) {
+        // update the absolutePoses
+        for (int i = 0; i < (int)_relativePoses.size(); ++i) {
             auto parentIndex = _skeleton->getParentIndex((int)i);
             if (parentIndex != -1) {
                 absolutePoses[i] = absolutePoses[parentIndex] * _relativePoses[i];
@@ -236,7 +234,9 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I
     for (auto& target: targets) {
         int tipIndex = target.getIndex();
         int parentIndex = _skeleton->getParentIndex(tipIndex);
-        if (parentIndex != -1) {
+
+        // update rotationOnly targets that don't lie on the ik chain of other ik targets.
+        if (parentIndex != -1 && !_accumulators[tipIndex].isDirty() && target.getType() == IKTarget::Type::RotationOnly) {
             const glm::quat& targetRotation = target.getRotation();
             // compute tip's new parent-relative rotation
             // Q = Qp * q   -->   q' = Qp^ * Q
@@ -254,24 +254,25 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I
     }
 }
 
-int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVec& absolutePoses) {
-    int lowestMovedIndex = (int)_relativePoses.size();
+void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug) {
+    size_t chainDepth = 0;
+
     IKTarget::Type targetType = target.getType();
     if (targetType == IKTarget::Type::RotationOnly) {
         // the final rotation will be enforced after the iterations
         // TODO: solve this correctly
-        return lowestMovedIndex;
+        return;
     }
 
     int tipIndex = target.getIndex();
     int pivotIndex = _skeleton->getParentIndex(tipIndex);
     if (pivotIndex == -1 || pivotIndex == _hipsIndex) {
-        return lowestMovedIndex;
+        return;
     }
     int pivotsParentIndex = _skeleton->getParentIndex(pivotIndex);
     if (pivotsParentIndex == -1) {
         // TODO?: handle case where tip's parent is root?
-        return lowestMovedIndex;
+        return;
     }
 
     // cache tip's absolute orientation
@@ -281,31 +282,46 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe
     // the tip's parent-relative as we proceed up the chain
     glm::quat tipParentOrientation = absolutePoses[pivotIndex].rot();
 
+    std::map<int, DebugJoint> debugJointMap;
+
     // NOTE: if this code is removed, the head will remain rigid, causing the spine/hips to thrust forward backward
     // as the head is nodded.
-    if (targetType == IKTarget::Type::HmdHead) {
+    if (targetType == IKTarget::Type::HmdHead ||
+        targetType == IKTarget::Type::RotationAndPosition ||
+        targetType == IKTarget::Type::HipsRelativeRotationAndPosition) {
 
-        // rotate tip directly to target orientation
-        tipOrientation = target.getRotation();
-        glm::quat tipRelativeRotation = glm::inverse(tipParentOrientation) * tipOrientation;
+        // rotate tip toward target orientation
+        glm::quat deltaRot = target.getRotation() * glm::inverse(tipOrientation);
+
+        deltaRot *= target.getFlexCoefficient(chainDepth);
+        glm::normalize(deltaRot);
+
+        // compute parent relative rotation
+        glm::quat tipRelativeRotation = glm::inverse(tipParentOrientation) * deltaRot * tipOrientation;
 
         // then enforce tip's constraint
         RotationConstraint* constraint = getConstraint(tipIndex);
+        bool constrained = false;
         if (constraint) {
-            bool constrained = constraint->apply(tipRelativeRotation);
+            constrained = constraint->apply(tipRelativeRotation);
             if (constrained) {
                 tipOrientation = tipParentOrientation * tipRelativeRotation;
                 tipRelativeRotation = tipRelativeRotation;
             }
         }
+
         // store the relative rotation change in the accumulator
         _accumulators[tipIndex].add(tipRelativeRotation, target.getWeight());
+
+        if (debug) {
+            debugJointMap[tipIndex] = DebugJoint(tipRelativeRotation, constrained);
+        }
     }
 
     // cache tip absolute position
     glm::vec3 tipPosition = absolutePoses[tipIndex].trans();
 
-    size_t chainDepth = 1;
+    chainDepth++;
 
     // descend toward root, pivoting each joint to get tip closer to target position
     while (pivotIndex != _hipsIndex && pivotsParentIndex != -1) {
@@ -388,15 +404,15 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe
 
         // compute joint's new parent-relative rotation after swing
         // Q' = dQ * Q   and   Q = Qp * q   -->   q' = Qp^ * dQ * Q
-        glm::quat newRot = glm::normalize(glm::inverse(
-                absolutePoses[pivotsParentIndex].rot()) *
-                deltaRotation *
-                absolutePoses[pivotIndex].rot());
+        glm::quat newRot = glm::normalize(glm::inverse(absolutePoses[pivotsParentIndex].rot()) *
+                                          deltaRotation *
+                                          absolutePoses[pivotIndex].rot());
 
         // enforce pivot's constraint
         RotationConstraint* constraint = getConstraint(pivotIndex);
+        bool constrained = false;
         if (constraint) {
-            bool constrained = constraint->apply(newRot);
+            constrained = constraint->apply(newRot);
             if (constrained) {
                 // the constraint will modify the local rotation of the tip so we must
                 // compute the corresponding model-frame deltaRotation
@@ -408,9 +424,8 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe
         // store the relative rotation change in the accumulator
         _accumulators[pivotIndex].add(newRot, target.getWeight());
 
-        // this joint has been changed so we check to see if it has the lowest index
-        if (pivotIndex < lowestMovedIndex) {
-            lowestMovedIndex = pivotIndex;
+        if (debug) {
+            debugJointMap[pivotIndex] = DebugJoint(newRot, constrained);
         }
 
         // keep track of tip's new transform as we descend towards root
@@ -423,7 +438,10 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe
 
         chainDepth++;
     }
-    return lowestMovedIndex;
+
+    if (debug) {
+        debugDrawIKChain(debugJointMap, context);
+    }
 }
 
 //virtual
@@ -551,7 +569,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
 
             {
                 PROFILE_RANGE_EX(simulation_animation, "ik/ccd", 0xffff00ff, 0);
-                solveWithCyclicCoordinateDescent(targets);
+                solveWithCyclicCoordinateDescent(context, targets);
             }
 
             if (_hipsTargetIndex < 0) {
@@ -562,6 +580,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
             }
         }
     }
+
     return _relativePoses;
 }
 
@@ -658,8 +677,8 @@ static void setEllipticalSwingLimits(SwingTwistConstraint* stConstraint, float l
     float dTheta = TWO_PI / NUM_SUBDIVISIONS;
     float theta = 0.0f;
     for (int i = 0; i < NUM_SUBDIVISIONS; i++) {
-        float theta_prime = atanf((lateralSwingPhi / anteriorSwingPhi) * tanf(theta));
-        float phi = (cosf(2.0f * theta_prime) * ((lateralSwingPhi - anteriorSwingPhi) / 2.0f)) + ((lateralSwingPhi + anteriorSwingPhi) / 2.0f);
+        float theta_prime = atanf((anteriorSwingPhi / lateralSwingPhi) * tanf(theta));
+        float phi = (cosf(2.0f * theta_prime) * ((anteriorSwingPhi - lateralSwingPhi) / 2.0f)) + ((anteriorSwingPhi + lateralSwingPhi) / 2.0f);
         minDots.push_back(cosf(phi));
         theta += dTheta;
     }
@@ -771,27 +790,36 @@ void AnimInverseKinematics::initConstraints() {
             std::vector<glm::vec3> swungDirections;
             float deltaTheta = PI / 4.0f;
             float theta = 0.0f;
-            swungDirections.push_back(glm::vec3(mirror * cosf(theta), -0.25f, sinf(theta)));
+            swungDirections.push_back(glm::vec3(mirror * cosf(theta), 1.0f, sinf(theta))); // posterior
             theta += deltaTheta;
-            swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.0f, sinf(theta)));
+            swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.5f, sinf(theta)));
             theta += deltaTheta;
-            swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.25f, sinf(theta))); // posterior
+            swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.25f, sinf(theta)));
             theta += deltaTheta;
-            swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.0f, sinf(theta)));
+            swungDirections.push_back(glm::vec3(mirror * cosf(theta), -1.5f, sinf(theta)));
             theta += deltaTheta;
-            swungDirections.push_back(glm::vec3(mirror * cosf(theta), -0.25f, sinf(theta)));
+            swungDirections.push_back(glm::vec3(mirror * cosf(theta), -3.0f, sinf(theta))); // anterior
             theta += deltaTheta;
-            swungDirections.push_back(glm::vec3(mirror * cosf(theta), -0.5f, sinf(theta)));
+            swungDirections.push_back(glm::vec3(mirror * cosf(theta), -1.5f, sinf(theta)));
             theta += deltaTheta;
-            swungDirections.push_back(glm::vec3(mirror * cosf(theta), -0.5f, sinf(theta))); // anterior
+            swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.25f, sinf(theta)));
             theta += deltaTheta;
-            swungDirections.push_back(glm::vec3(mirror * cosf(theta), -0.5f, sinf(theta)));
+            swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.5f, sinf(theta)));
 
             std::vector<float> minDots;
             for (size_t i = 0; i < swungDirections.size(); i++) {
                 minDots.push_back(glm::dot(glm::normalize(swungDirections[i]), Vectors::UNIT_Y));
             }
             stConstraint->setSwingLimits(minDots);
+
+            /*
+            // simple cone
+            std::vector<float> minDots;
+            const float MAX_HAND_SWING = 2.9f; // 170 deg //2 * PI / 3.0f;
+            minDots.push_back(cosf(MAX_HAND_SWING));
+            stConstraint->setSwingLimits(minDots);
+            */
+
             constraint = static_cast<RotationConstraint*>(stConstraint);
         } else if (0 == baseName.compare("Hand", Qt::CaseSensitive)) {
             SwingTwistConstraint* stConstraint = new SwingTwistConstraint();
@@ -839,11 +867,11 @@ void AnimInverseKinematics::initConstraints() {
         } else if (baseName.startsWith("Shoulder", Qt::CaseSensitive)) {
             SwingTwistConstraint* stConstraint = new SwingTwistConstraint();
             stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot());
-            const float MAX_SHOULDER_TWIST = PI / 20.0f;
+            const float MAX_SHOULDER_TWIST = PI / 10.0f;
             stConstraint->setTwistLimits(-MAX_SHOULDER_TWIST, MAX_SHOULDER_TWIST);
 
             std::vector<float> minDots;
-            const float MAX_SHOULDER_SWING = PI / 16.0f;
+            const float MAX_SHOULDER_SWING = PI / 12.0f;
             minDots.push_back(cosf(MAX_SHOULDER_SWING));
             stConstraint->setSwingLimits(minDots);
 
@@ -855,8 +883,8 @@ void AnimInverseKinematics::initConstraints() {
             stConstraint->setTwistLimits(-MAX_SPINE_TWIST, MAX_SPINE_TWIST);
 
             // limit lateral swings more then forward-backward swings
-            const float MAX_SPINE_LATERAL_SWING = PI / 30.0f;
-            const float MAX_SPINE_ANTERIOR_SWING = PI / 20.0f;
+            const float MAX_SPINE_LATERAL_SWING = PI / 15.0f;
+            const float MAX_SPINE_ANTERIOR_SWING = PI / 10.0f;
             setEllipticalSwingLimits(stConstraint, MAX_SPINE_LATERAL_SWING, MAX_SPINE_ANTERIOR_SWING);
 
             if (0 == baseName.compare("Spine1", Qt::CaseSensitive)
@@ -869,12 +897,12 @@ void AnimInverseKinematics::initConstraints() {
         } else if (0 == baseName.compare("Neck", Qt::CaseSensitive)) {
             SwingTwistConstraint* stConstraint = new SwingTwistConstraint();
             stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot());
-            const float MAX_NECK_TWIST = PI / 10.0f;
+            const float MAX_NECK_TWIST = PI / 8.0f;
             stConstraint->setTwistLimits(-MAX_NECK_TWIST, MAX_NECK_TWIST);
 
             // limit lateral swings more then forward-backward swings
-            const float MAX_NECK_LATERAL_SWING = PI / 10.0f;
-            const float MAX_NECK_ANTERIOR_SWING = PI / 8.0f;
+            const float MAX_NECK_LATERAL_SWING = PI / 12.0f;
+            const float MAX_NECK_ANTERIOR_SWING = PI / 10.0f;
             setEllipticalSwingLimits(stConstraint, MAX_NECK_LATERAL_SWING, MAX_NECK_ANTERIOR_SWING);
 
             constraint = static_cast<RotationConstraint*>(stConstraint);
@@ -884,10 +912,10 @@ void AnimInverseKinematics::initConstraints() {
             const float MAX_HEAD_TWIST = PI / 6.0f;
             stConstraint->setTwistLimits(-MAX_HEAD_TWIST, MAX_HEAD_TWIST);
 
-            std::vector<float> minDots;
-            const float MAX_HEAD_SWING = PI / 6.0f;
-            minDots.push_back(cosf(MAX_HEAD_SWING));
-            stConstraint->setSwingLimits(minDots);
+            // limit lateral swings more then forward-backward swings
+            const float MAX_NECK_LATERAL_SWING = PI / 4.0f;
+            const float MAX_NECK_ANTERIOR_SWING = PI / 3.0f;
+            setEllipticalSwingLimits(stConstraint, MAX_NECK_LATERAL_SWING, MAX_NECK_ANTERIOR_SWING);
 
             constraint = static_cast<RotationConstraint*>(stConstraint);
         } else if (0 == baseName.compare("ForeArm", Qt::CaseSensitive)) {
@@ -1044,7 +1072,96 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele
 static glm::vec3 sphericalToCartesian(float phi, float theta) {
     float cos_phi = cosf(phi);
     float sin_phi = sinf(phi);
-    return glm::vec3(sin_phi * cosf(theta), cos_phi, -sin_phi * sinf(theta));
+    return glm::vec3(sin_phi * cosf(theta), cos_phi, sin_phi * sinf(theta));
+}
+
+void AnimInverseKinematics::debugDrawRelativePoses(const AnimContext& context) const {
+    AnimPoseVec poses = _relativePoses;
+
+    // convert relative poses to absolute
+    _skeleton->convertRelativePosesToAbsolute(poses);
+
+    mat4 geomToWorldMatrix = context.getRigToWorldMatrix() * context.getGeometryToRigMatrix();
+
+    const vec4 RED(1.0f, 0.0f, 0.0f, 1.0f);
+    const vec4 GREEN(0.0f, 1.0f, 0.0f, 1.0f);
+    const vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f);
+    const vec4 GRAY(0.2f, 0.2f, 0.2f, 1.0f);
+    const float AXIS_LENGTH = 2.0f; // cm
+
+    // draw each pose
+    for (int i = 0; i < (int)poses.size(); i++) {
+
+        // transform local axes into world space.
+        auto pose = poses[i];
+        glm::vec3 xAxis = transformVectorFast(geomToWorldMatrix, pose.rot() * Vectors::UNIT_X);
+        glm::vec3 yAxis = transformVectorFast(geomToWorldMatrix, pose.rot() * Vectors::UNIT_Y);
+        glm::vec3 zAxis = transformVectorFast(geomToWorldMatrix, pose.rot() * Vectors::UNIT_Z);
+        glm::vec3 pos = transformPoint(geomToWorldMatrix, pose.trans());
+        DebugDraw::getInstance().drawRay(pos, pos + AXIS_LENGTH * xAxis, RED);
+        DebugDraw::getInstance().drawRay(pos, pos + AXIS_LENGTH * yAxis, GREEN);
+        DebugDraw::getInstance().drawRay(pos, pos + AXIS_LENGTH * zAxis, BLUE);
+
+        // draw line to parent
+        int parentIndex = _skeleton->getParentIndex(i);
+        if (parentIndex != -1) {
+            glm::vec3 parentPos = transformPoint(geomToWorldMatrix, poses[parentIndex].trans());
+            DebugDraw::getInstance().drawRay(pos, parentPos, GRAY);
+        }
+    }
+}
+
+void AnimInverseKinematics::debugDrawIKChain(std::map<int, DebugJoint>& debugJointMap, const AnimContext& context) const {
+    AnimPoseVec poses = _relativePoses;
+
+    // copy debug joint rotations into the relative poses
+    for (auto& debugJoint : debugJointMap) {
+        poses[debugJoint.first].rot() = debugJoint.second.relRot;
+    }
+
+    // convert relative poses to absolute
+    _skeleton->convertRelativePosesToAbsolute(poses);
+
+    mat4 geomToWorldMatrix = context.getRigToWorldMatrix() * context.getGeometryToRigMatrix();
+
+    const vec4 RED(1.0f, 0.0f, 0.0f, 1.0f);
+    const vec4 GREEN(0.0f, 1.0f, 0.0f, 1.0f);
+    const vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f);
+    const vec4 GRAY(0.2f, 0.2f, 0.2f, 1.0f);
+    const float AXIS_LENGTH = 2.0f; // cm
+
+    // draw each pose
+    for (int i = 0; i < (int)poses.size(); i++) {
+
+        // only draw joints that are actually in debugJointMap, or their parents
+        auto iter = debugJointMap.find(i);
+        auto parentIter = debugJointMap.find(_skeleton->getParentIndex(i));
+        if (iter != debugJointMap.end() || parentIter != debugJointMap.end()) {
+
+            // transform local axes into world space.
+            auto pose = poses[i];
+            glm::vec3 xAxis = transformVectorFast(geomToWorldMatrix, pose.rot() * Vectors::UNIT_X);
+            glm::vec3 yAxis = transformVectorFast(geomToWorldMatrix, pose.rot() * Vectors::UNIT_Y);
+            glm::vec3 zAxis = transformVectorFast(geomToWorldMatrix, pose.rot() * Vectors::UNIT_Z);
+            glm::vec3 pos = transformPoint(geomToWorldMatrix, pose.trans());
+            DebugDraw::getInstance().drawRay(pos, pos + AXIS_LENGTH * xAxis, RED);
+            DebugDraw::getInstance().drawRay(pos, pos + AXIS_LENGTH * yAxis, GREEN);
+            DebugDraw::getInstance().drawRay(pos, pos + AXIS_LENGTH * zAxis, BLUE);
+
+            // draw line to parent
+            int parentIndex = _skeleton->getParentIndex(i);
+            if (parentIndex != -1) {
+                glm::vec3 parentPos = transformPoint(geomToWorldMatrix, poses[parentIndex].trans());
+                glm::vec4 color = GRAY;
+
+                // draw constrained joints with a RED link to their parent.
+                if (parentIter != debugJointMap.end() && parentIter->second.constrained) {
+                    color = RED;
+                }
+                DebugDraw::getInstance().drawRay(pos, parentPos, color);
+            }
+        }
+    }
 }
 
 void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) const {
@@ -1056,20 +1173,12 @@ void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) con
         const vec4 CYAN(0.0f, 1.0f, 1.0f, 1.0f);
         const vec4 GRAY(0.2f, 0.2f, 0.2f, 1.0f);
         const vec4 MAGENTA(1.0f, 0.0f, 1.0f, 1.0f);
-        const float AXIS_LENGTH = 2.0f; // cm
+        const float AXIS_LENGTH = 5.0f; // cm
         const float TWIST_LENGTH = 4.0f; // cm
-        const float HINGE_LENGTH = 6.0f; // cm
-        const float SWING_LENGTH = 5.0f; // cm
+        const float HINGE_LENGTH = 4.0f; // cm
+        const float SWING_LENGTH = 4.0f; // cm
 
-        AnimPoseVec poses = _skeleton->getRelativeDefaultPoses();
-
-        // copy reference rotations into the relative poses
-        for (int i = 0; i < (int)poses.size(); i++) {
-            const RotationConstraint* constraint = getConstraint(i);
-            if (constraint) {
-                poses[i].rot() = constraint->getReferenceRotation();
-            }
-        }
+        AnimPoseVec poses = _relativePoses;
 
         // convert relative poses to absolute
         _skeleton->convertRelativePosesToAbsolute(poses);
@@ -1127,8 +1236,8 @@ void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) con
                         glm::vec3 hingeAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * refRot * Vectors::UNIT_Y);
                         DebugDraw::getInstance().drawRay(pos, pos + HINGE_LENGTH * hingeAxis, MAGENTA);
 
-                        glm::quat minRot = glm::angleAxis(swingTwistConstraint->getMinTwist(), Vectors::UNIT_Y);
-                        glm::quat maxRot = glm::angleAxis(swingTwistConstraint->getMaxTwist(), Vectors::UNIT_Y);
+                        glm::quat minRot = glm::angleAxis(swingTwistConstraint->getMinTwist(), refRot * Vectors::UNIT_Y);
+                        glm::quat maxRot = glm::angleAxis(swingTwistConstraint->getMaxTwist(), refRot * Vectors::UNIT_Y);
 
                         const int NUM_SWING_STEPS = 10;
                         for (int i = 0; i < NUM_SWING_STEPS + 1; i++) {
@@ -1140,17 +1249,18 @@ void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) con
                         // draw swing constraints.
                         const size_t NUM_MIN_DOTS = swingTwistConstraint->getMinDots().size();
                         const float D_THETA = TWO_PI / (NUM_MIN_DOTS - 1);
+                        const float PI_2 = PI / 2.0f;
                         float theta = 0.0f;
                         for (size_t i = 0, j = NUM_MIN_DOTS - 2; i < NUM_MIN_DOTS - 1; j = i, i++, theta += D_THETA) {
                             // compute swing rotation from theta and phi angles.
                             float phi = acosf(swingTwistConstraint->getMinDots()[i]);
-                            glm::vec3 swungAxis = sphericalToCartesian(phi, theta);
+                            glm::vec3 swungAxis = sphericalToCartesian(phi, theta - PI_2);
                             glm::vec3 worldSwungAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * refRot * swungAxis);
                             glm::vec3 swingTip = pos + SWING_LENGTH * worldSwungAxis;
 
                             float prevPhi = acos(swingTwistConstraint->getMinDots()[j]);
                             float prevTheta = theta - D_THETA;
-                            glm::vec3 prevSwungAxis = sphericalToCartesian(prevPhi, prevTheta);
+                            glm::vec3 prevSwungAxis = sphericalToCartesian(prevPhi, prevTheta - PI_2);
                             glm::vec3 prevWorldSwungAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * refRot * prevSwungAxis);
                             glm::vec3 prevSwingTip = pos + SWING_LENGTH * prevWorldSwungAxis;
 
@@ -1159,7 +1269,6 @@ void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) con
                         }
                     }
                 }
-                pose.rot() = constraint->computeCenterRotation();
             }
         }
     }
diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h
index 74face6d0b..0267f14650 100644
--- a/libraries/animation/src/AnimInverseKinematics.h
+++ b/libraries/animation/src/AnimInverseKinematics.h
@@ -58,13 +58,22 @@ public:
 
 protected:
     void computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses);
-    void solveWithCyclicCoordinateDescent(const std::vector<IKTarget>& targets);
-    int solveTargetWithCCD(const IKTarget& target, AnimPoseVec& absolutePoses);
+    void solveWithCyclicCoordinateDescent(const AnimContext& context, const std::vector<IKTarget>& targets);
+    void solveTargetWithCCD(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug);
     virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override;
+    struct DebugJoint {
+        DebugJoint() : relRot(), constrained(false) {}
+        DebugJoint(const glm::quat& relRotIn, bool constrainedIn) : relRot(relRotIn), constrained(constrainedIn) {}
+        glm::quat relRot;
+        bool constrained;
+    };
+    void debugDrawIKChain(std::map<int, DebugJoint>& debugJointMap, const AnimContext& context) const;
+    void debugDrawRelativePoses(const AnimContext& context) const;
     void debugDrawConstraints(const AnimContext& context) const;
     void initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPose);
     void blendToPoses(const AnimPoseVec& targetPoses, const AnimPoseVec& underPose, float blendFactor);
 
+
     // for AnimDebugDraw rendering
     virtual const AnimPoseVec& getPosesInternal() const override { return _relativePoses; }
 
diff --git a/libraries/animation/src/IKTarget.cpp b/libraries/animation/src/IKTarget.cpp
index c67c0621c3..2fe767b08d 100644
--- a/libraries/animation/src/IKTarget.cpp
+++ b/libraries/animation/src/IKTarget.cpp
@@ -23,7 +23,6 @@ void IKTarget::setFlexCoefficients(size_t numFlexCoefficientsIn, const float* fl
 
 float IKTarget::getFlexCoefficient(size_t chainDepth) const {
     const float DEFAULT_FLEX_COEFFICIENT = 0.5f;
-
     if (chainDepth < _numFlexCoefficients) {
         return _flexCoefficients[chainDepth];
     } else {
diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp
index 23db05eb73..99d2deb323 100644
--- a/libraries/animation/src/Rig.cpp
+++ b/libraries/animation/src/Rig.cpp
@@ -954,7 +954,7 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons
         updateAnimationStateHandlers();
         _animVars.setRigToGeometryTransform(_rigToGeometryTransform);
 
-        AnimContext context(_enableDebugDrawIKTargets, _enableDebugDrawIKConstraints,
+        AnimContext context(_enableDebugDrawIKTargets, _enableDebugDrawIKConstraints, _enableDebugDrawIKChains,
                             getGeometryToRigTransform(), rigToWorldTransform);
 
         // evaluate the animation
@@ -1403,7 +1403,6 @@ void Rig::computeAvatarBoundingCapsule(
     AnimInverseKinematics ikNode("boundingShape");
     ikNode.setSkeleton(_animSkeleton);
 
-    // AJT: FIX ME!!!!! ensure that empty weights vector does something reasonable....
     ikNode.setTargetVars("LeftHand",
                          "leftHandPosition",
                          "leftHandRotation",
@@ -1452,7 +1451,7 @@ void Rig::computeAvatarBoundingCapsule(
 
     // call overlay twice: once to verify AnimPoseVec joints and again to do the IK
     AnimNode::Triggers triggersOut;
-    AnimContext context(false, false, glm::mat4(), glm::mat4());
+    AnimContext context(false, false, false, glm::mat4(), glm::mat4());
     float dt = 1.0f; // the value of this does not matter
     ikNode.overlay(animVars, context, dt, triggersOut, _animSkeleton->getRelativeBindPoses());
     AnimPoseVec finalPoses =  ikNode.overlay(animVars, context, dt, triggersOut, _animSkeleton->getRelativeBindPoses());
diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h
index 33b66f91ea..18780d6e64 100644
--- a/libraries/animation/src/Rig.h
+++ b/libraries/animation/src/Rig.h
@@ -27,7 +27,6 @@
 
 class Rig;
 class AnimInverseKinematics;
-typedef std::shared_ptr<Rig> RigPointer;
 
 // Rig instances are reentrant.
 // However only specific methods thread-safe.  Noted below.
@@ -232,6 +231,7 @@ public:
 
     void setEnableDebugDrawIKTargets(bool enableDebugDrawIKTargets) { _enableDebugDrawIKTargets = enableDebugDrawIKTargets; }
     void setEnableDebugDrawIKConstraints(bool enableDebugDrawIKConstraints) { _enableDebugDrawIKConstraints = enableDebugDrawIKConstraints; }
+    void setEnableDebugDrawIKChains(bool enableDebugDrawIKChains) { _enableDebugDrawIKChains = enableDebugDrawIKChains; }
 
     // input assumed to be in rig space
     void computeHeadFromHMD(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut) const;
@@ -343,6 +343,7 @@ protected:
 
     bool _enableDebugDrawIKTargets { false };
     bool _enableDebugDrawIKConstraints { false };
+    bool _enableDebugDrawIKChains { false };
 
 private:
     QMap<int, StateHandler> _stateHandlers;
diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp
index 9864b3f67a..e03ca83131 100644
--- a/libraries/audio-client/src/AudioClient.cpp
+++ b/libraries/audio-client/src/AudioClient.cpp
@@ -1516,6 +1516,7 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice
 
     // cleanup any previously initialized device
     if (_audioOutput) {
+        _audioOutputIODevice.close();
         _audioOutput->stop();
 
         //must be deleted in next eventloop cycle when its called from notify()
diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h
index 9793ee3288..b79cee238c 100644
--- a/libraries/audio-client/src/AudioClient.h
+++ b/libraries/audio-client/src/AudioClient.h
@@ -94,7 +94,6 @@ public:
             _audio(audio), _unfulfilledReads(0) {}
 
         void start() { open(QIODevice::ReadOnly | QIODevice::Unbuffered); }
-        void stop() { close(); }
         qint64 readData(char * data, qint64 maxSize) override;
         qint64 writeData(const char * data, qint64 maxSize) override { return 0; }
         int getRecentUnfulfilledReads() { int unfulfilledReads = _unfulfilledReads; _unfulfilledReads = 0; return unfulfilledReads; }
diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
index 1968a731a4..d78287a0e7 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
+++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
@@ -98,7 +98,7 @@ void Avatar::setShowNamesAboveHeads(bool show) {
     showNamesAboveHeads = show;
 }
 
-Avatar::Avatar(QThread* thread, RigPointer rig) :
+Avatar::Avatar(QThread* thread) :
     _voiceSphereID(GeometryCache::UNKNOWN_ID)
 {
     // we may have been created in the network thread, but we live in the main thread
@@ -344,9 +344,9 @@ void Avatar::simulate(float deltaTime, bool inView) {
         if (inView) {
             Head* head = getHead();
             if (_hasNewJointData) {
-                _skeletonModel->getRig()->copyJointsFromJointData(_jointData);
+                _skeletonModel->getRig().copyJointsFromJointData(_jointData);
                 glm::mat4 rootTransform = glm::scale(_skeletonModel->getScale()) * glm::translate(_skeletonModel->getOffset());
-                _skeletonModel->getRig()->computeExternalPoses(rootTransform);
+                _skeletonModel->getRig().computeExternalPoses(rootTransform);
                 _jointDataSimulationRate.increment();
 
                 _skeletonModel->simulate(deltaTime, true);
@@ -684,7 +684,8 @@ void Avatar::simulateAttachments(float deltaTime) {
                 _skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRotation)) {
                 model->setTranslation(jointPosition + jointRotation * attachment.translation * getUniformScale());
                 model->setRotation(jointRotation * attachment.rotation);
-                model->setScaleToFit(true, getUniformScale() * attachment.scale, true); // hack to force rescale
+                float scale = getUniformScale() * 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);
@@ -907,17 +908,16 @@ glm::vec3 Avatar::getDefaultJointTranslation(int index) const {
 
 glm::quat Avatar::getAbsoluteDefaultJointRotationInObjectFrame(int index) const {
     glm::quat rotation;
-    auto rig = _skeletonModel->getRig();
-    glm::quat rot = rig->getAnimSkeleton()->getAbsoluteDefaultPose(index).rot();
+    glm::quat rot = _skeletonModel->getRig().getAnimSkeleton()->getAbsoluteDefaultPose(index).rot();
     return Quaternions::Y_180 * rot;
 }
 
 glm::vec3 Avatar::getAbsoluteDefaultJointTranslationInObjectFrame(int index) const {
     glm::vec3 translation;
-    auto rig = _skeletonModel->getRig();
-    glm::vec3 trans = rig->getAnimSkeleton()->getAbsoluteDefaultPose(index).trans();
+    const Rig& rig = _skeletonModel->getRig();
+    glm::vec3 trans = rig.getAnimSkeleton()->getAbsoluteDefaultPose(index).trans();
     glm::mat4 y180Mat = createMatFromQuatAndPos(Quaternions::Y_180, glm::vec3());
-    return transformPoint(y180Mat * rig->getGeometryToRigTransform(), trans);
+    return transformPoint(y180Mat * rig.getGeometryToRigTransform(), trans);
 }
 
 glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const {
@@ -1083,16 +1083,16 @@ void Avatar::setModelURLFinished(bool success) {
 
 
 // create new model, can return an instance of a SoftAttachmentModel rather then Model
-static std::shared_ptr<Model> allocateAttachmentModel(bool isSoft, RigPointer rigOverride, bool isCauterized) {
+static std::shared_ptr<Model> allocateAttachmentModel(bool isSoft, const Rig& rigOverride, bool isCauterized) {
     if (isSoft) {
         // cast to std::shared_ptr<Model>
-        std::shared_ptr<SoftAttachmentModel> softModel = std::make_shared<SoftAttachmentModel>(std::make_shared<Rig>(), nullptr, rigOverride);
+        std::shared_ptr<SoftAttachmentModel> softModel = std::make_shared<SoftAttachmentModel>(nullptr, rigOverride);
         if (isCauterized) {
             softModel->flagAsCauterized();
         }
         return std::dynamic_pointer_cast<Model>(softModel);
     } else {
-        return std::make_shared<Model>(std::make_shared<Rig>());
+        return std::make_shared<Model>();
     }
 }
 
@@ -1409,21 +1409,19 @@ void Avatar::setParentJointIndex(quint16 parentJointIndex) {
 QList<QVariant> Avatar::getSkeleton() {
     SkeletonModelPointer skeletonModel = _skeletonModel;
     if (skeletonModel) {
-        RigPointer rig = skeletonModel->getRig();
-        if (rig) {
-            AnimSkeleton::ConstPointer skeleton = rig->getAnimSkeleton();
-            if (skeleton) {
-                QList<QVariant> list;
-                list.reserve(skeleton->getNumJoints());
-                for (int i = 0; i < skeleton->getNumJoints(); i++) {
-                    QVariantMap obj;
-                    obj["name"] = skeleton->getJointName(i);
-                    obj["index"] = i;
-                    obj["parentIndex"] = skeleton->getParentIndex(i);
-                    list.push_back(obj);
-                }
-                return list;
+        const Rig& rig = skeletonModel->getRig();
+        AnimSkeleton::ConstPointer skeleton = rig.getAnimSkeleton();
+        if (skeleton) {
+            QList<QVariant> list;
+            list.reserve(skeleton->getNumJoints());
+            for (int i = 0; i < skeleton->getNumJoints(); i++) {
+                QVariantMap obj;
+                obj["name"] = skeleton->getJointName(i);
+                obj["index"] = i;
+                obj["parentIndex"] = skeleton->getParentIndex(i);
+                list.push_back(obj);
             }
+            return list;
         }
     }
 
diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h
index ae24caca29..1724d42510 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h
+++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h
@@ -75,7 +75,7 @@ public:
     static void setShowCollisionShapes(bool render);
     static void setShowNamesAboveHeads(bool show);
 
-    explicit Avatar(QThread* thread, RigPointer rig = nullptr);
+    explicit Avatar(QThread* thread);
     ~Avatar();
 
     virtual void instantiableAvatar() = 0;
diff --git a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp
index b4b0929c0c..96ecd86ff4 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp
+++ b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp
@@ -23,8 +23,6 @@
 
 #include "Avatar.h"
 
-const float NORMAL_HZ = 60.0f; // the update rate the constant values were tuned for
-
 using namespace std;
 
 static bool disableEyelidAdjustment { false };
@@ -43,7 +41,9 @@ void Head::reset() {
     _baseYaw = _basePitch = _baseRoll = 0.0f;
 }
 
-void Head::computeAudioLoudness(float deltaTime) {
+void Head::simulate(float deltaTime) {
+    const float NORMAL_HZ = 60.0f; // the update rate the constant values were tuned for
+
     // grab the audio loudness from the owning avatar, if we have one
     float audioLoudness = _owningAvatar ? _owningAvatar->getAudioLoudness() : 0.0f;
 
@@ -58,99 +58,102 @@ void Head::computeAudioLoudness(float deltaTime) {
         _longTermAverageLoudness = glm::mix(_longTermAverageLoudness, _averageLoudness, glm::min(deltaTime / AUDIO_LONG_TERM_AVERAGING_SECS, 1.0f));
     }
 
-    float audioAttackAveragingRate = (10.0f - deltaTime * NORMAL_HZ) / 10.0f; // --> 0.9 at 60 Hz
-    _audioAttack = audioAttackAveragingRate * _audioAttack +
-        (1.0f - audioAttackAveragingRate) * fabs((audioLoudness - _longTermAverageLoudness) - _lastLoudness);
-    _lastLoudness = (audioLoudness - _longTermAverageLoudness);
-}
+    if (!_isFaceTrackerConnected) {
+        if (!_isEyeTrackerConnected) {
+            // Update eye saccades
+            const float AVERAGE_MICROSACCADE_INTERVAL = 1.0f;
+            const float AVERAGE_SACCADE_INTERVAL = 6.0f;
+            const float MICROSACCADE_MAGNITUDE = 0.002f;
+            const float SACCADE_MAGNITUDE = 0.04f;
+            const float NOMINAL_FRAME_RATE = 60.0f;
 
-void Head::computeEyeMovement(float deltaTime) {
-    // Update eye saccades
-    const float AVERAGE_MICROSACCADE_INTERVAL = 1.0f;
-    const float AVERAGE_SACCADE_INTERVAL = 6.0f;
-    const float MICROSACCADE_MAGNITUDE = 0.002f;
-    const float SACCADE_MAGNITUDE = 0.04f;
-    const float NOMINAL_FRAME_RATE = 60.0f;
+            if (randFloat() < deltaTime / AVERAGE_MICROSACCADE_INTERVAL) {
+                _saccadeTarget = MICROSACCADE_MAGNITUDE * randVector();
+            } else if (randFloat() < deltaTime / AVERAGE_SACCADE_INTERVAL) {
+                _saccadeTarget = SACCADE_MAGNITUDE * randVector();
+            }
+            _saccade += (_saccadeTarget - _saccade) * pow(0.5f, NOMINAL_FRAME_RATE * deltaTime);
+        } else {
+            _saccade = glm::vec3();
+        }
 
-    if (randFloat() < deltaTime / AVERAGE_MICROSACCADE_INTERVAL) {
-        _saccadeTarget = MICROSACCADE_MAGNITUDE * randVector();
-    } else if (randFloat() < deltaTime / AVERAGE_SACCADE_INTERVAL) {
-        _saccadeTarget = SACCADE_MAGNITUDE * randVector();
-    }
-    _saccade += (_saccadeTarget - _saccade) * pow(0.5f, NOMINAL_FRAME_RATE * deltaTime);
+        // Detect transition from talking to not; force blink after that and a delay
+        bool forceBlink = false;
+        const float TALKING_LOUDNESS = 100.0f;
+        const float BLINK_AFTER_TALKING = 0.25f;
+        _timeWithoutTalking += deltaTime;
+        if ((_averageLoudness - _longTermAverageLoudness) > TALKING_LOUDNESS) {
+            _timeWithoutTalking = 0.0f;
+        } else if (_timeWithoutTalking - deltaTime < BLINK_AFTER_TALKING && _timeWithoutTalking >= BLINK_AFTER_TALKING) {
+            forceBlink = true;
+        }
 
-    // Detect transition from talking to not; force blink after that and a delay
-    bool forceBlink = false;
-    const float TALKING_LOUDNESS = 100.0f;
-    const float BLINK_AFTER_TALKING = 0.25f;
-    _timeWithoutTalking += deltaTime;
-    if ((_averageLoudness - _longTermAverageLoudness) > TALKING_LOUDNESS) {
-        _timeWithoutTalking = 0.0f;
-    } else if (_timeWithoutTalking - deltaTime < BLINK_AFTER_TALKING && _timeWithoutTalking >= BLINK_AFTER_TALKING) {
-        forceBlink = true;
-    }
+        // Update audio attack data for facial animation (eyebrows and mouth)
+        float audioAttackAveragingRate = (10.0f - deltaTime * NORMAL_HZ) / 10.0f; // --> 0.9 at 60 Hz
+        _audioAttack = audioAttackAveragingRate * _audioAttack +
+            (1.0f - audioAttackAveragingRate) * fabs((audioLoudness - _longTermAverageLoudness) - _lastLoudness);
+        _lastLoudness = (audioLoudness - _longTermAverageLoudness);
 
-    const float BLINK_SPEED = 10.0f;
-    const float BLINK_SPEED_VARIABILITY = 1.0f;
-    const float BLINK_START_VARIABILITY = 0.25f;
-    const float FULLY_OPEN = 0.0f;
-    const float FULLY_CLOSED = 1.0f;
-    if (_leftEyeBlinkVelocity == 0.0f && _rightEyeBlinkVelocity == 0.0f) {
-        // no blinking when brows are raised; blink less with increasing loudness
-        const float BASE_BLINK_RATE = 15.0f / 60.0f;
-        const float ROOT_LOUDNESS_TO_BLINK_INTERVAL = 0.25f;
-        if (forceBlink || (_browAudioLift < EPSILON && shouldDo(glm::max(1.0f, sqrt(fabs(_averageLoudness - _longTermAverageLoudness)) *
-                ROOT_LOUDNESS_TO_BLINK_INTERVAL) / BASE_BLINK_RATE, deltaTime))) {
-            _leftEyeBlinkVelocity = BLINK_SPEED + randFloat() * BLINK_SPEED_VARIABILITY;
-            _rightEyeBlinkVelocity = BLINK_SPEED + randFloat() * BLINK_SPEED_VARIABILITY;
-            if (randFloat() < 0.5f) {
-                _leftEyeBlink = BLINK_START_VARIABILITY;
-            } else {
-                _rightEyeBlink = BLINK_START_VARIABILITY;
+        const float BROW_LIFT_THRESHOLD = 100.0f;
+        if (_audioAttack > BROW_LIFT_THRESHOLD) {
+            _browAudioLift += sqrtf(_audioAttack) * 0.01f;
+        }
+        _browAudioLift = glm::clamp(_browAudioLift *= 0.7f, 0.0f, 1.0f);
+
+        const float BLINK_SPEED = 10.0f;
+        const float BLINK_SPEED_VARIABILITY = 1.0f;
+        const float BLINK_START_VARIABILITY = 0.25f;
+        const float FULLY_OPEN = 0.0f;
+        const float FULLY_CLOSED = 1.0f;
+        if (_leftEyeBlinkVelocity == 0.0f && _rightEyeBlinkVelocity == 0.0f) {
+            // no blinking when brows are raised; blink less with increasing loudness
+            const float BASE_BLINK_RATE = 15.0f / 60.0f;
+            const float ROOT_LOUDNESS_TO_BLINK_INTERVAL = 0.25f;
+            if (forceBlink || (_browAudioLift < EPSILON && shouldDo(glm::max(1.0f, sqrt(fabs(_averageLoudness - _longTermAverageLoudness)) *
+                    ROOT_LOUDNESS_TO_BLINK_INTERVAL) / BASE_BLINK_RATE, deltaTime))) {
+                _leftEyeBlinkVelocity = BLINK_SPEED + randFloat() * BLINK_SPEED_VARIABILITY;
+                _rightEyeBlinkVelocity = BLINK_SPEED + randFloat() * BLINK_SPEED_VARIABILITY;
+                if (randFloat() < 0.5f) {
+                    _leftEyeBlink = BLINK_START_VARIABILITY;
+                } else {
+                    _rightEyeBlink = BLINK_START_VARIABILITY;
+                }
+            }
+        } else {
+            _leftEyeBlink = glm::clamp(_leftEyeBlink + _leftEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED);
+            _rightEyeBlink = glm::clamp(_rightEyeBlink + _rightEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED);
+
+            if (_leftEyeBlink == FULLY_CLOSED) {
+                _leftEyeBlinkVelocity = -BLINK_SPEED;
+
+            } else if (_leftEyeBlink == FULLY_OPEN) {
+                _leftEyeBlinkVelocity = 0.0f;
+            }
+            if (_rightEyeBlink == FULLY_CLOSED) {
+                _rightEyeBlinkVelocity = -BLINK_SPEED;
+
+            } else if (_rightEyeBlink == FULLY_OPEN) {
+                _rightEyeBlinkVelocity = 0.0f;
             }
         }
+
+        // use data to update fake Faceshift blendshape coefficients
+        calculateMouthShapes(deltaTime);
+        FaceTracker::updateFakeCoefficients(_leftEyeBlink,
+                                            _rightEyeBlink,
+                                            _browAudioLift,
+                                            _audioJawOpen,
+                                            _mouth2,
+                                            _mouth3,
+                                            _mouth4,
+                                            _transientBlendshapeCoefficients);
+
+        applyEyelidOffset(getOrientation());
+
     } else {
-        _leftEyeBlink = glm::clamp(_leftEyeBlink + _leftEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED);
-        _rightEyeBlink = glm::clamp(_rightEyeBlink + _rightEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED);
-
-        if (_leftEyeBlink == FULLY_CLOSED) {
-            _leftEyeBlinkVelocity = -BLINK_SPEED;
-
-        } else if (_leftEyeBlink == FULLY_OPEN) {
-            _leftEyeBlinkVelocity = 0.0f;
-        }
-        if (_rightEyeBlink == FULLY_CLOSED) {
-            _rightEyeBlinkVelocity = -BLINK_SPEED;
-
-        } else if (_rightEyeBlink == FULLY_OPEN) {
-            _rightEyeBlinkVelocity = 0.0f;
-        }
+        _saccade = glm::vec3();
     }
 
-    applyEyelidOffset(getOrientation());
-}
-
-void Head::computeFaceMovement(float deltaTime) {
-    // Update audio attack data for facial animation (eyebrows and mouth)
-    const float BROW_LIFT_THRESHOLD = 100.0f;
-    if (_audioAttack > BROW_LIFT_THRESHOLD) {
-        _browAudioLift += sqrtf(_audioAttack) * 0.01f;
-    }
-    _browAudioLift = glm::clamp(_browAudioLift *= 0.7f, 0.0f, 1.0f);
-
-    // use data to update fake Faceshift blendshape coefficients
-    calculateMouthShapes(deltaTime);
-    FaceTracker::updateFakeCoefficients(_leftEyeBlink,
-                                        _rightEyeBlink,
-                                        _browAudioLift,
-                                        _audioJawOpen,
-                                        _mouth2,
-                                        _mouth3,
-                                        _mouth4,
-                                        _transientBlendshapeCoefficients);
-}
-
-void Head::computeEyePosition() {
     _leftEyePosition = _rightEyePosition = getPosition();
     if (_owningAvatar) {
         auto skeletonModel = static_cast<Avatar*>(_owningAvatar)->getSkeletonModel();
@@ -161,13 +164,6 @@ void Head::computeEyePosition() {
     _eyePosition = 0.5f * (_leftEyePosition + _rightEyePosition);
 }
 
-void Head::simulate(float deltaTime) {
-    computeAudioLoudness(deltaTime);
-    computeFaceMovement(deltaTime);
-    computeEyeMovement(deltaTime);
-    computeEyePosition();
-}
-
 void Head::calculateMouthShapes(float deltaTime) {
     const float JAW_OPEN_SCALE = 0.015f;
     const float JAW_OPEN_RATE = 0.9f;
diff --git a/libraries/avatars-renderer/src/avatars-renderer/Head.h b/libraries/avatars-renderer/src/avatars-renderer/Head.h
index 39331500b5..c5902285b9 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/Head.h
+++ b/libraries/avatars-renderer/src/avatars-renderer/Head.h
@@ -83,11 +83,6 @@ public:
     float getTimeWithoutTalking() const { return _timeWithoutTalking; }
 
 protected:
-    void computeAudioLoudness(float deltaTime);
-    void computeEyeMovement(float deltaTime);
-    void computeFaceMovement(float deltaTime);
-    void computeEyePosition();
-
     // disallow copies of the Head, copy of owning Avatar is disallowed too
     Head(const Head&);
     Head& operator= (const Head&);
diff --git a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp
index ad69ba24cb..e870e2de12 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp
+++ b/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp
@@ -8,9 +8,9 @@
 
 #include "OtherAvatar.h"
 
-OtherAvatar::OtherAvatar(QThread* thread, RigPointer rig) : Avatar(thread, rig) {
+OtherAvatar::OtherAvatar(QThread* thread) : Avatar(thread) {
     // give the pointer to our head to inherited _headData variable from AvatarData
     _headData = new Head(this);
-    _skeletonModel = std::make_shared<SkeletonModel>(this, nullptr, rig);
+    _skeletonModel = std::make_shared<SkeletonModel>(this, nullptr);
     connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished);
 }
diff --git a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.h b/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.h
index 22a7e1863a..df09d7fd99 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.h
+++ b/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.h
@@ -13,7 +13,7 @@
 
 class OtherAvatar : public Avatar {
 public:
-    explicit OtherAvatar(QThread* thread, RigPointer rig = nullptr);
+    explicit OtherAvatar(QThread* thread);
     virtual void instantiableAvatar() override {};
 };
 
diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp
index d3453280ac..2a2817e68b 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp
+++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp
@@ -22,8 +22,8 @@
 #include "Avatar.h"
 #include "Logging.h"
 
-SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent, RigPointer rig) :
-    CauterizedModel(rig, parent),
+SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) :
+    CauterizedModel(parent),
     _owningAvatar(owningAvatar),
     _boundingCapsuleLocalOffset(0.0f),
     _boundingCapsuleRadius(0.0f),
@@ -31,7 +31,6 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent, RigPointer r
     _defaultEyeModelPosition(glm::vec3(0.0f, 0.0f, 0.0f)),
     _headClipDistance(DEFAULT_NEAR_CLIP)
 {
-    assert(_rig);
     assert(_owningAvatar);
 }
 
@@ -41,12 +40,12 @@ SkeletonModel::~SkeletonModel() {
 void SkeletonModel::initJointStates() {
     const FBXGeometry& geometry = getFBXGeometry();
     glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset);
-    _rig->initJointStates(geometry, modelOffset);
+    _rig.initJointStates(geometry, modelOffset);
 
     // Determine the default eye position for avatar scale = 1.0
     int headJointIndex = geometry.headJointIndex;
-    if (0 > headJointIndex || headJointIndex >= _rig->getJointStateCount()) {
-        qCWarning(avatars_renderer) << "Bad head joint! Got:" << headJointIndex << "jointCount:" << _rig->getJointStateCount();
+    if (0 > headJointIndex || headJointIndex >= _rig.getJointStateCount()) {
+        qCWarning(avatars_renderer) << "Bad head joint! Got:" << headJointIndex << "jointCount:" << _rig.getJointStateCount();
     }
     glm::vec3 leftEyePosition, rightEyePosition;
     getEyeModelPositions(leftEyePosition, rightEyePosition);
@@ -102,7 +101,7 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
 
     // If the head is not positioned, updateEyeJoints won't get the math right
     glm::quat headOrientation;
-    _rig->getJointRotation(geometry.headJointIndex, headOrientation);
+    _rig.getJointRotation(geometry.headJointIndex, headOrientation);
     glm::vec3 eulers = safeEulerAngles(headOrientation);
     head->setBasePitch(glm::degrees(-eulers.x));
     head->setBaseYaw(glm::degrees(eulers.y));
@@ -116,7 +115,7 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
     eyeParams.leftEyeJointIndex = geometry.leftEyeJointIndex;
     eyeParams.rightEyeJointIndex = geometry.rightEyeJointIndex;
 
-    _rig->updateFromEyeParameters(eyeParams);
+    _rig.updateFromEyeParameters(eyeParams);
 }
 
 void SkeletonModel::updateAttitude() {
@@ -136,7 +135,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
 
         // let rig compute the model offset
         glm::vec3 registrationPoint;
-        if (_rig->getModelRegistrationPoint(registrationPoint)) {
+        if (_rig.getModelRegistrationPoint(registrationPoint)) {
             setOffset(registrationPoint);
         }
     } else {
@@ -164,8 +163,8 @@ bool operator<(const IndexValue& firstIndex, const IndexValue& secondIndex) {
 }
 
 bool SkeletonModel::getLeftGrabPosition(glm::vec3& position) const {
-    int knuckleIndex = _rig->indexOfJoint("LeftHandMiddle1");
-    int handIndex = _rig->indexOfJoint("LeftHand");
+    int knuckleIndex = _rig.indexOfJoint("LeftHandMiddle1");
+    int handIndex = _rig.indexOfJoint("LeftHand");
     if (knuckleIndex >= 0 && handIndex >= 0) {
         glm::quat handRotation;
         glm::vec3 knucklePosition;
@@ -189,8 +188,8 @@ bool SkeletonModel::getLeftGrabPosition(glm::vec3& position) const {
 }
 
 bool SkeletonModel::getRightGrabPosition(glm::vec3& position) const {
-    int knuckleIndex = _rig->indexOfJoint("RightHandMiddle1");
-    int handIndex = _rig->indexOfJoint("RightHand");
+    int knuckleIndex = _rig.indexOfJoint("RightHandMiddle1");
+    int handIndex = _rig.indexOfJoint("RightHand");
     if (knuckleIndex >= 0 && handIndex >= 0) {
         glm::quat handRotation;
         glm::vec3 knucklePosition;
@@ -304,7 +303,7 @@ float VERY_BIG_MASS = 1.0e6f;
 
 // virtual
 void SkeletonModel::computeBoundingShape() {
-    if (!isLoaded() || _rig->jointStatesEmpty()) {
+    if (!isLoaded() || _rig.jointStatesEmpty()) {
         return;
     }
 
@@ -316,7 +315,7 @@ void SkeletonModel::computeBoundingShape() {
 
     float radius, height;
     glm::vec3 offset;
-    _rig->computeAvatarBoundingCapsule(geometry, radius, height, offset);
+    _rig.computeAvatarBoundingCapsule(geometry, radius, height, offset);
     float invScale = 1.0f / _owningAvatar->getUniformScale();
     _boundingCapsuleRadius = invScale * radius;
     _boundingCapsuleHeight = invScale * height;
diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h
index 059dd245fd..db87a37477 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h
+++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h
@@ -28,7 +28,7 @@ class SkeletonModel : public CauterizedModel {
 
 public:
 
-    SkeletonModel(Avatar* owningAvatar, QObject* parent = nullptr, RigPointer rig = nullptr);
+    SkeletonModel(Avatar* owningAvatar, QObject* parent = nullptr);
     ~SkeletonModel();
 
     void initJointStates() override;
diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp
index 3aa5ab69fa..d82068b8ac 100644
--- a/libraries/avatars/src/AvatarData.cpp
+++ b/libraries/avatars/src/AvatarData.cpp
@@ -2030,17 +2030,6 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) {
         version = JSON_AVATAR_JOINT_ROTATIONS_IN_RELATIVE_FRAME_VERSION;
     }
 
-    // The head setOrientation likes to overwrite the avatar orientation,
-    // so lets do the head first
-    // Most head data is relative to the avatar, and needs no basis correction,
-    // but the lookat vector does need correction
-    if (json.contains(JSON_AVATAR_HEAD)) {
-        if (!_headData) {
-            _headData = new HeadData(this);
-        }
-        _headData->fromJson(json[JSON_AVATAR_HEAD].toObject());
-    }
-
     if (json.contains(JSON_AVATAR_BODY_MODEL)) {
         auto bodyModelURL = json[JSON_AVATAR_BODY_MODEL].toString();
         if (useFrameSkeleton && bodyModelURL != getSkeletonModelURL().toString()) {
@@ -2079,6 +2068,14 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) {
         setOrientation(currentBasis->getRotation());
     }
 
+    // Do after avatar orientation because head look-at needs avatar orientation.
+    if (json.contains(JSON_AVATAR_HEAD)) {
+        if (!_headData) {
+            _headData = new HeadData(this);
+        }
+        _headData->fromJson(json[JSON_AVATAR_HEAD].toObject());
+    }
+
     if (json.contains(JSON_AVATAR_SCALE)) {
         setTargetScale((float)json[JSON_AVATAR_SCALE].toDouble());
     }
diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp
index 2704b6539c..8ae33a1b4f 100644
--- a/libraries/avatars/src/HeadData.cpp
+++ b/libraries/avatars/src/HeadData.cpp
@@ -52,6 +52,13 @@ glm::quat HeadData::getOrientation() const {
     return _owningAvatar->getOrientation() * getRawOrientation();
 }
 
+void HeadData::setHeadOrientation(const glm::quat& orientation) {
+    glm::quat bodyOrientation = _owningAvatar->getOrientation();
+    glm::vec3 eulers = glm::degrees(safeEulerAngles(glm::inverse(bodyOrientation) * orientation));
+    _basePitch = eulers.x;
+    _baseYaw = eulers.y;
+    _baseRoll = eulers.z;
+}
 
 void HeadData::setOrientation(const glm::quat& orientation) {
     // rotate body about vertical axis
@@ -61,10 +68,7 @@ void HeadData::setOrientation(const glm::quat& orientation) {
     _owningAvatar->setOrientation(bodyOrientation);
 
     // the rest goes to the head
-    glm::vec3 eulers = glm::degrees(safeEulerAngles(glm::inverse(bodyOrientation) * orientation));
-    _basePitch = eulers.x;
-    _baseYaw = eulers.y;
-    _baseRoll = eulers.z;
+    setHeadOrientation(orientation);
 }
 
 //Lazily construct a lookup map from the blendshapes
@@ -173,14 +177,14 @@ void HeadData::fromJson(const QJsonObject& json) {
         }
     }
 
-    if (json.contains(JSON_AVATAR_HEAD_ROTATION)) {
-        setOrientation(quatFromJsonValue(json[JSON_AVATAR_HEAD_ROTATION]));
-    }
-
     if (json.contains(JSON_AVATAR_HEAD_LOOKAT)) {
         auto relativeLookAt = vec3FromJsonValue(json[JSON_AVATAR_HEAD_LOOKAT]);
         if (glm::length2(relativeLookAt) > 0.01f) {
             setLookAtPosition((_owningAvatar->getOrientation() * relativeLookAt) + _owningAvatar->getPosition());
         }
     }
+
+    if (json.contains(JSON_AVATAR_HEAD_ROTATION)) {
+        setHeadOrientation(quatFromJsonValue(json[JSON_AVATAR_HEAD_ROTATION]));
+    }
 }
diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h
index be9d54e93e..0bb38c1dad 100644
--- a/libraries/avatars/src/HeadData.h
+++ b/libraries/avatars/src/HeadData.h
@@ -101,6 +101,8 @@ private:
     // privatize copy ctor and assignment operator so copies of this object cannot be made
     HeadData(const HeadData&);
     HeadData& operator= (const HeadData&);
+
+    void setHeadOrientation(const glm::quat& orientation);
 };
 
 #endif // hifi_HeadData_h
diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp
index 1de476c825..09308baabb 100644
--- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp
+++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp
@@ -582,7 +582,7 @@ ModelPointer EntityTreeRenderer::allocateModel(const QString& url, float loading
         return model;
     }
 
-    model = std::make_shared<Model>(std::make_shared<Rig>(), nullptr, spatiallyNestableOverride);
+    model = std::make_shared<Model>(nullptr, spatiallyNestableOverride);
     model->setLoadingPriority(loadingPriority);
     model->init();
     model->setURL(QUrl(url));
diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
index 8b1ba75259..36273c1f07 100644
--- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
@@ -807,7 +807,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
             const FBXMesh& mesh = fbxGeometry.meshes.at(i);
             if (mesh.clusters.size() > 0) {
                 const FBXCluster& cluster = mesh.clusters.at(0);
-                auto jointMatrix = _model->getRig()->getJointTransform(cluster.jointIndex);
+                auto jointMatrix = _model->getRig().getJointTransform(cluster.jointIndex);
                 // we backtranslate by the registration offset so we can apply that offset to the shapeInfo later
                 localTransforms.push_back(invRegistraionOffset * jointMatrix * cluster.inverseBindMatrix);
             } else {
@@ -1080,26 +1080,22 @@ bool RenderableModelEntityItem::setAbsoluteJointRotationInObjectFrame(int index,
     if (!_model) {
         return false;
     }
-    RigPointer rig = _model->getRig();
-    if (!rig) {
-        return false;
-    }
-
-    int jointParentIndex = rig->getJointParentIndex(index);
+    const Rig& rig = _model->getRig();
+    int jointParentIndex = rig.getJointParentIndex(index);
     if (jointParentIndex == -1) {
         return setLocalJointRotation(index, rotation);
     }
 
     bool success;
     AnimPose jointParentPose;
-    success = rig->getAbsoluteJointPoseInRigFrame(jointParentIndex, jointParentPose);
+    success = rig.getAbsoluteJointPoseInRigFrame(jointParentIndex, jointParentPose);
     if (!success) {
         return false;
     }
     AnimPose jointParentInversePose = jointParentPose.inverse();
 
     AnimPose jointAbsolutePose; // in rig frame
-    success = rig->getAbsoluteJointPoseInRigFrame(index, jointAbsolutePose);
+    success = rig.getAbsoluteJointPoseInRigFrame(index, jointAbsolutePose);
     if (!success) {
         return false;
     }
@@ -1113,26 +1109,23 @@ bool RenderableModelEntityItem::setAbsoluteJointTranslationInObjectFrame(int ind
     if (!_model) {
         return false;
     }
-    RigPointer rig = _model->getRig();
-    if (!rig) {
-        return false;
-    }
+    const Rig& rig = _model->getRig();
 
-    int jointParentIndex = rig->getJointParentIndex(index);
+    int jointParentIndex = rig.getJointParentIndex(index);
     if (jointParentIndex == -1) {
         return setLocalJointTranslation(index, translation);
     }
 
     bool success;
     AnimPose jointParentPose;
-    success = rig->getAbsoluteJointPoseInRigFrame(jointParentIndex, jointParentPose);
+    success = rig.getAbsoluteJointPoseInRigFrame(jointParentIndex, jointParentPose);
     if (!success) {
         return false;
     }
     AnimPose jointParentInversePose = jointParentPose.inverse();
 
     AnimPose jointAbsolutePose; // in rig frame
-    success = rig->getAbsoluteJointPoseInRigFrame(index, jointAbsolutePose);
+    success = rig.getAbsoluteJointPoseInRigFrame(index, jointAbsolutePose);
     if (!success) {
         return false;
     }
@@ -1248,20 +1241,16 @@ void RenderableModelEntityItem::locationChanged(bool tellPhysics) {
 }
 
 int RenderableModelEntityItem::getJointIndex(const QString& name) const {
-    if (_model && _model->isActive()) {
-        RigPointer rig = _model->getRig();
-        return rig->indexOfJoint(name);
-    }
-    return -1;
+    return (_model && _model->isActive()) ? _model->getRig().indexOfJoint(name) : -1;
 }
 
 QStringList RenderableModelEntityItem::getJointNames() const {
     QStringList result;
     if (_model && _model->isActive()) {
-        RigPointer rig = _model->getRig();
-        int jointCount = rig->getJointStateCount();
+        const Rig& rig = _model->getRig();
+        int jointCount = rig.getJointStateCount();
         for (int jointIndex = 0; jointIndex < jointCount; jointIndex++) {
-            result << rig->nameOfJoint(jointIndex);
+            result << rig.nameOfJoint(jointIndex);
         }
     }
     return result;
diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp
index 66495a7054..6556f18776 100644
--- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp
@@ -36,15 +36,17 @@ void RenderableZoneEntityItem::changeProperties(Lambda setNewProperties) {
     QString oldShapeURL = getCompoundShapeURL();
     glm::vec3 oldPosition = getPosition(), oldDimensions = getDimensions();
     glm::quat oldRotation = getRotation();
-    
+
     setNewProperties();
-    
+
     if (oldShapeURL != getCompoundShapeURL()) {
         if (_model) {
-            delete _model;
+            _model.reset();
         }
-        
-        _model = getModel();
+
+        _model = std::make_shared<Model>();
+        _model->setIsWireframe(true);
+        _model->init();
         _needsInitialSimulation = true;
         _model->setURL(getCompoundShapeURL());
     }
@@ -80,35 +82,24 @@ int RenderableZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch
     return bytesRead;
 }
 
-Model* RenderableZoneEntityItem::getModel() {
-    Model* model = new Model(nullptr);
-    model->setIsWireframe(true);
-    model->init();
-    return model;
-}
-
-void RenderableZoneEntityItem::initialSimulation() {
-    _model->setScaleToFit(true, getDimensions());
-    _model->setSnapModelToRegistrationPoint(true, getRegistrationPoint());
-    _model->setRotation(getRotation());
-    _model->setTranslation(getPosition());
-    _model->simulate(0.0f);
-    _needsInitialSimulation = false;
-}
-
 void RenderableZoneEntityItem::updateGeometry() {
     if (_model && !_model->isActive() && hasCompoundShapeURL()) {
         // Since we have a delayload, we need to update the geometry if it has been downloaded
         _model->setURL(getCompoundShapeURL());
     }
     if (_model && _model->isActive() && _needsInitialSimulation) {
-        initialSimulation();
+        _model->setScaleToFit(true, getDimensions());
+        _model->setSnapModelToRegistrationPoint(true, getRegistrationPoint());
+        _model->setRotation(getRotation());
+        _model->setTranslation(getPosition());
+        _model->simulate(0.0f);
+        _needsInitialSimulation = false;
     }
 }
 
 void RenderableZoneEntityItem::render(RenderArgs* args) {
     Q_ASSERT(getType() == EntityTypes::Zone);
-    
+
     if (_drawZoneBoundaries) {
         switch (getShapeType()) {
             case SHAPE_TYPE_COMPOUND: {
@@ -123,9 +114,9 @@ void RenderableZoneEntityItem::render(RenderArgs* args) {
                     render::Item::Status::Getters statusGetters;
                     makeEntityItemStatusGetters(getThisPointer(), statusGetters);
                     _model->addToScene(scene, transaction);
-                    
+
                     scene->enqueueTransaction(transaction);
-                    
+
                     _model->setVisibleInScene(getVisible(), scene);
                 }
                 break;
@@ -134,7 +125,7 @@ void RenderableZoneEntityItem::render(RenderArgs* args) {
             case SHAPE_TYPE_SPHERE: {
                 PerformanceTimer perfTimer("zone->renderPrimitive");
                 glm::vec4 DEFAULT_COLOR(1.0f, 1.0f, 1.0f, 1.0f);
-                
+
                 Q_ASSERT(args->_batch);
                 gpu::Batch& batch = *args->_batch;
 
@@ -159,7 +150,7 @@ void RenderableZoneEntityItem::render(RenderArgs* args) {
                 break;
         }
     }
-    
+
     if ((!_drawZoneBoundaries || getShapeType() != SHAPE_TYPE_COMPOUND) &&
         _model && !_model->needsFixupInScene()) {
         // If the model is in the scene but doesn't need to be, remove it.
@@ -175,11 +166,11 @@ bool RenderableZoneEntityItem::contains(const glm::vec3& point) const {
         return EntityItem::contains(point);
     }
     const_cast<RenderableZoneEntityItem*>(this)->updateGeometry();
-    
+
     if (_model && _model->isActive() && EntityItem::contains(point)) {
         return _model->convexHullContains(point);
     }
-    
+
     return false;
 }
 
@@ -188,7 +179,7 @@ public:
     RenderableZoneEntityItemMeta(EntityItemPointer entity) : entity(entity){ }
     typedef render::Payload<RenderableZoneEntityItemMeta> Payload;
     typedef Payload::DataPointer Pointer;
-    
+
     EntityItemPointer entity;
 };
 
@@ -196,7 +187,7 @@ namespace render {
     template <> const ItemKey payloadGetKey(const RenderableZoneEntityItemMeta::Pointer& payload) {
         return ItemKey::Builder::opaqueShape();
     }
-    
+
     template <> const Item::Bound payloadGetBound(const RenderableZoneEntityItemMeta::Pointer& payload) {
         if (payload && payload->entity) {
             bool success;
@@ -220,7 +211,7 @@ namespace render {
 bool RenderableZoneEntityItem::addToScene(EntityItemPointer self, const render::ScenePointer& scene,
                                            render::Transaction& transaction) {
     _myMetaItem = scene->allocateID();
-    
+
     auto renderData = std::make_shared<RenderableZoneEntityItemMeta>(self);
     auto renderPayload = std::make_shared<RenderableZoneEntityItemMeta::Payload>(renderData);
 
diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h
index c81afdab08..7241e34ce8 100644
--- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h
+++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h
@@ -23,7 +23,7 @@ public:
     
     RenderableZoneEntityItem(const EntityItemID& entityItemID) :
         ZoneEntityItem(entityItemID),
-        _model(NULL),
+        _model(nullptr),
         _needsInitialSimulation(true)
     { }
     
@@ -48,14 +48,12 @@ private:
     virtual void dimensionsChanged() override { EntityItem::dimensionsChanged(); notifyBoundChanged(); }
     void notifyBoundChanged();
 
-    Model* getModel();
-    void initialSimulation();
     void updateGeometry();
     
     template<typename Lambda>
     void changeProperties(Lambda functor);
     
-    Model* _model;
+    ModelPointer _model;
     bool _needsInitialSimulation;
     
     render::ItemID _myMetaItem{ render::Item::INVALID_ITEM_ID };
diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp
index 14122594fe..1d83365102 100644
--- a/libraries/entities/src/EntityItem.cpp
+++ b/libraries/entities/src/EntityItem.cpp
@@ -810,7 +810,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
     READ_ENTITY_PROPERTY(PROP_COLLISIONLESS, bool, updateCollisionless);
     READ_ENTITY_PROPERTY(PROP_COLLISION_MASK, uint8_t, updateCollisionMask);
     READ_ENTITY_PROPERTY(PROP_DYNAMIC, bool, updateDynamic);
-    READ_ENTITY_PROPERTY(PROP_LOCKED, bool, setLocked);
+    READ_ENTITY_PROPERTY(PROP_LOCKED, bool, updateLocked);
     READ_ENTITY_PROPERTY(PROP_USER_DATA, QString, setUserData);
 
     if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_MARKETPLACE_ID) {
@@ -1344,6 +1344,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
     SET_ENTITY_PROPERTY_FROM_PROPERTIES(dynamic, updateDynamic);
     SET_ENTITY_PROPERTY_FROM_PROPERTIES(created, updateCreated);
     SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifetime, updateLifetime);
+    SET_ENTITY_PROPERTY_FROM_PROPERTIES(locked, updateLocked);
 
     // non-simulation properties below
     SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, setScript);
@@ -1352,7 +1353,6 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
     SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionSoundURL, setCollisionSoundURL);
     SET_ENTITY_PROPERTY_FROM_PROPERTIES(localRenderAlpha, setLocalRenderAlpha);
     SET_ENTITY_PROPERTY_FROM_PROPERTIES(visible, setVisible);
-    SET_ENTITY_PROPERTY_FROM_PROPERTIES(locked, setLocked);
     SET_ENTITY_PROPERTY_FROM_PROPERTIES(userData, setUserData);
     SET_ENTITY_PROPERTY_FROM_PROPERTIES(marketplaceID, setMarketplaceID);
     SET_ENTITY_PROPERTY_FROM_PROPERTIES(name, setName);
@@ -2769,6 +2769,23 @@ void EntityItem::setLocked(bool value) {
     });
 }
 
+void EntityItem::updateLocked(bool value) {
+    bool changed { false };
+    withWriteLock([&] {
+        if (_locked != value) {
+            _locked = value;
+            changed = true;
+        }
+    });
+    if (changed) {
+        markDirtyFlags(Simulation::DIRTY_MOTION_TYPE);
+        EntityTreePointer tree = getTree();
+        if (tree) {
+            tree->entityChanged(getThisPointer());
+        }
+    }
+}
+
 QString EntityItem::getUserData() const { 
     QString result;
     withReadLock([&] {
diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h
index 1896893b52..ba6077592e 100644
--- a/libraries/entities/src/EntityItem.h
+++ b/libraries/entities/src/EntityItem.h
@@ -305,6 +305,7 @@ public:
 
     bool getLocked() const;
     void setLocked(bool value);
+    void updateLocked(bool value);
 
     QString getUserData() const;
     virtual void setUserData(const QString& value);
diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp
index 6975d017b0..11cddf2634 100644
--- a/libraries/entities/src/EntityTree.cpp
+++ b/libraries/entities/src/EntityTree.cpp
@@ -957,6 +957,24 @@ void EntityTree::bumpTimestamp(EntityItemProperties& properties) { //fixme put c
     properties.setLastEdited(properties.getLastEdited() + LAST_EDITED_SERVERSIDE_BUMP);
 }
 
+bool EntityTree::isScriptInWhitelist(const QString& scriptProperty) {
+
+    // grab a URL representation of the entity script so we can check the host for this script
+    auto entityScriptURL = QUrl::fromUserInput(scriptProperty);
+
+    for (const auto& whiteListedPrefix : _entityScriptSourceWhitelist) {
+        auto whiteListURL = QUrl::fromUserInput(whiteListedPrefix);
+
+        // check if this script URL matches the whitelist domain and, optionally, is beneath the path
+        if (entityScriptURL.host().compare(whiteListURL.host(), Qt::CaseInsensitive) == 0 &&
+            entityScriptURL.path().startsWith(whiteListURL.path(), Qt::CaseInsensitive)) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
 int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength,
                                      const SharedNodePointer& senderNode) {
 
@@ -986,7 +1004,8 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
             quint64 startFilter = 0, endFilter = 0;
             quint64 startLogging = 0, endLogging = 0;
 
-            bool suppressDisallowedScript = false;
+            bool suppressDisallowedClientScript = false;
+            bool suppressDisallowedServerScript = false;
             bool isPhysics = message.getType() == PacketType::EntityPhysics;
 
             _totalEditMessages++;
@@ -1011,36 +1030,57 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
                 }
             }
 
-            if (validEditPacket && !_entityScriptSourceWhitelist.isEmpty() && !properties.getScript().isEmpty()) {
-                bool passedWhiteList = false;
+            if (validEditPacket && !_entityScriptSourceWhitelist.isEmpty()) {
 
-                // grab a URL representation of the entity script so we can check the host for this script
-                auto entityScriptURL = QUrl::fromUserInput(properties.getScript());
+                bool wasDeletedBecauseOfClientScript = false;
 
-                for (const auto& whiteListedPrefix : _entityScriptSourceWhitelist) {
-                    auto whiteListURL = QUrl::fromUserInput(whiteListedPrefix);
+                // check the client entity script to make sure its URL is in the whitelist
+                if (!properties.getScript().isEmpty()) {
+                    bool clientScriptPassedWhitelist = isScriptInWhitelist(properties.getScript());
 
-                    // check if this script URL matches the whitelist domain and, optionally, is beneath the path
-                    if (entityScriptURL.host().compare(whiteListURL.host(), Qt::CaseInsensitive) == 0 &&
-                        entityScriptURL.path().startsWith(whiteListURL.path(), Qt::CaseInsensitive)) {
-                        passedWhiteList = true;
-                        break;
+                    if (!clientScriptPassedWhitelist) {
+                        if (wantEditLogging()) {
+                            qCDebug(entities) << "User [" << senderNode->getUUID()
+                                << "] attempting to set entity script not on whitelist, edit rejected";
+                        }
+
+                        // If this was an add, we also want to tell the client that sent this edit that the entity was not added.
+                        if (isAdd) {
+                            QWriteLocker locker(&_recentlyDeletedEntitiesLock);
+                            _recentlyDeletedEntityItemIDs.insert(usecTimestampNow(), entityItemID);
+                            validEditPacket = false;
+                            wasDeletedBecauseOfClientScript = true;
+                        } else {
+                            suppressDisallowedClientScript = true;
+                        }
                     }
                 }
-                if (!passedWhiteList) {
-                    if (wantEditLogging()) {
-                        qCDebug(entities) << "User [" << senderNode->getUUID() << "] attempting to set entity script not on whitelist, edit rejected";
-                    }
 
-                    // If this was an add, we also want to tell the client that sent this edit that the entity was not added.
-                    if (isAdd) {
-                        QWriteLocker locker(&_recentlyDeletedEntitiesLock);
-                        _recentlyDeletedEntityItemIDs.insert(usecTimestampNow(), entityItemID);
-                        validEditPacket = passedWhiteList;
-                    } else {
-                        suppressDisallowedScript = true;
+                // check all server entity scripts to make sure their URLs are in the whitelist
+                if (!properties.getServerScripts().isEmpty()) {
+                    bool serverScriptPassedWhitelist = isScriptInWhitelist(properties.getServerScripts());
+
+                    if (!serverScriptPassedWhitelist) {
+                        if (wantEditLogging()) {
+                            qCDebug(entities) << "User [" << senderNode->getUUID()
+                                << "] attempting to set server entity script not on whitelist, edit rejected";
+                        }
+
+                        // If this was an add, we also want to tell the client that sent this edit that the entity was not added.
+                        if (isAdd) {
+                            // Make sure we didn't already need to send back a delete because the client script failed
+                            // the whitelist check
+                            if (!wasDeletedBecauseOfClientScript) {
+                                QWriteLocker locker(&_recentlyDeletedEntitiesLock);
+                                _recentlyDeletedEntityItemIDs.insert(usecTimestampNow(), entityItemID);
+                                validEditPacket = false;
+                            }
+                        } else {
+                            suppressDisallowedServerScript = true;
+                        }
                     }
                 }
+
             }
 
             if ((isAdd || properties.lifetimeChanged()) &&
@@ -1075,11 +1115,16 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
 
                 if (existingEntity && !isAdd) {
 
-                    if (suppressDisallowedScript) {
+                    if (suppressDisallowedClientScript) {
                         bumpTimestamp(properties);
                         properties.setScript(existingEntity->getScript());
                     }
 
+                    if (suppressDisallowedServerScript) {
+                        bumpTimestamp(properties);
+                        properties.setServerScripts(existingEntity->getServerScripts());
+                    }
+
                     // if the EntityItem exists, then update it
                     startLogging = usecTimestampNow();
                     if (wantEditLogging()) {
diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h
index d7e069b005..c938c7e068 100644
--- a/libraries/entities/src/EntityTree.h
+++ b/libraries/entities/src/EntityTree.h
@@ -303,6 +303,8 @@ protected:
 
     void notifyNewlyCreatedEntity(const EntityItem& newEntity, const SharedNodePointer& senderNode);
 
+    bool isScriptInWhitelist(const QString& scriptURL);
+    
     QReadWriteLock _newlyCreatedHooksLock;
     QVector<NewlyCreatedEntityHook*> _newlyCreatedHooks;
 
diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp
index 12bfb8e70b..c57926feb4 100644
--- a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp
+++ b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp
@@ -18,11 +18,10 @@ bool GLTexelFormat::isCompressed() const {
         case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
         case GL_COMPRESSED_RED_RGTC1:
         case GL_COMPRESSED_RG_RGTC2:
+        case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:
             return true;
-            break;
         default:
             return false;
-            break;
     }
 }
 
@@ -238,6 +237,9 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) {
                 case gpu::COMPRESSED_BC5_XY:
                     result = GL_COMPRESSED_RG_RGTC2;
                     break;
+                case gpu::COMPRESSED_BC7_SRGBA:
+                    result = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM;
+                    break;
 
                 default:
                     qCWarning(gpugllogging) << "Unknown combination of texel format";
@@ -364,6 +366,9 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
             case gpu::COMPRESSED_BC5_XY:
                 texel.internalFormat = GL_COMPRESSED_RG_RGTC2;
                 break;
+            case gpu::COMPRESSED_BC7_SRGBA:
+                texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM;
+                break;
 
             default:
                 qCWarning(gpugllogging) << "Unknown combination of texel format";
@@ -634,6 +639,10 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
             case gpu::COMPRESSED_BC5_XY:
                 texel.internalFormat = GL_COMPRESSED_RG_RGTC2;
                 break;
+            case gpu::COMPRESSED_BC7_SRGBA:
+                texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM;
+                break;
+
             default:
                 qCWarning(gpugllogging) << "Unknown combination of texel format";
             }
diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
index 84dc49deba..b61a3b82c4 100644
--- a/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl/GLTexture.cpp
@@ -535,12 +535,14 @@ void GLVariableAllocationSupport::processWorkQueue(WorkQueue& workQueue) {
             vartexture->demote();
             workQueue.pop();
             addToWorkQueue(texture);
+            _memoryPressureStateStale = true;
             break;
 
         case MemoryPressureState::Undersubscribed:
             vartexture->promote();
             workQueue.pop();
             addToWorkQueue(texture);
+            _memoryPressureStateStale = true;
             break;
 
         case MemoryPressureState::Transfer:
diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
index 146554952e..968cc0e116 100644
--- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp
@@ -112,6 +112,7 @@ void GL41Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const
             case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
             case GL_COMPRESSED_RED_RGTC1:
             case GL_COMPRESSED_RG_RGTC2:
+            case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:
                 glCompressedTexSubImage2D(_target, mip, 0, yOffset, size.x, size.y, internalFormat,
                                           static_cast<GLsizei>(sourceSize), sourcePointer);
                 break;
@@ -128,6 +129,7 @@ void GL41Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const
             case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
             case GL_COMPRESSED_RED_RGTC1:
             case GL_COMPRESSED_RG_RGTC2:
+            case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:
                 glCompressedTexSubImage2D(target, mip, 0, yOffset, size.x, size.y, internalFormat,
                                           static_cast<GLsizei>(sourceSize), sourcePointer);
                 break;
diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp
index 120be923f5..442d456af9 100644
--- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp
+++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp
@@ -142,6 +142,7 @@ void GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const
             case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
             case GL_COMPRESSED_RED_RGTC1:
             case GL_COMPRESSED_RG_RGTC2:
+            case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:
                 glCompressedTextureSubImage2D(_id, mip, 0, yOffset, size.x, size.y, internalFormat,
                                               static_cast<GLsizei>(sourceSize), sourcePointer);
                 break;
@@ -156,6 +157,7 @@ void GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const
             case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
             case GL_COMPRESSED_RED_RGTC1:
             case GL_COMPRESSED_RG_RGTC2:
+            case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:
                 if (glCompressedTextureSubImage2DEXT) {
                     auto target = GLTexture::CUBE_FACE_LAYOUT[face];
                     glCompressedTextureSubImage2DEXT(_id, target, mip, 0, yOffset, size.x, size.y, internalFormat,
diff --git a/libraries/gpu/src/gpu/Format.cpp b/libraries/gpu/src/gpu/Format.cpp
index 43bcd35b88..a4b7411599 100644
--- a/libraries/gpu/src/gpu/Format.cpp
+++ b/libraries/gpu/src/gpu/Format.cpp
@@ -24,6 +24,7 @@ const Element Element::COLOR_COMPRESSED_SRGB{ VEC4, NUINT8, COMPRESSED_BC1_SRGB
 const Element Element::COLOR_COMPRESSED_SRGBA_MASK{ VEC4, NUINT8, COMPRESSED_BC1_SRGBA };
 const Element Element::COLOR_COMPRESSED_SRGBA{ VEC4, NUINT8, COMPRESSED_BC3_SRGBA };
 const Element Element::COLOR_COMPRESSED_XY{ VEC4, NUINT8, COMPRESSED_BC5_XY };
+const Element Element::COLOR_COMPRESSED_SRGBA_HIGH{ VEC4, NUINT8, COMPRESSED_BC7_SRGBA };
 
 const Element Element::VEC2NU8_XY{ VEC2, NUINT8, XY };
 
diff --git a/libraries/gpu/src/gpu/Format.h b/libraries/gpu/src/gpu/Format.h
index 8b7bbdcbed..250d705e18 100644
--- a/libraries/gpu/src/gpu/Format.h
+++ b/libraries/gpu/src/gpu/Format.h
@@ -163,6 +163,7 @@ enum Semantic {
     COMPRESSED_BC3_SRGBA,
     COMPRESSED_BC4_RED,
     COMPRESSED_BC5_XY,
+    COMPRESSED_BC7_SRGBA,
 
     _LAST_COMPRESSED,
 
@@ -234,6 +235,7 @@ public:
     static const Element COLOR_COMPRESSED_SRGBA_MASK;
     static const Element COLOR_COMPRESSED_SRGBA;
     static const Element COLOR_COMPRESSED_XY;
+    static const Element COLOR_COMPRESSED_SRGBA_HIGH;
     static const Element VEC2NU8_XY;
     static const Element VEC4F_COLOR_RGBA;
     static const Element VEC2F_UV;
diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp
index 92ead5f616..5f677d7424 100644
--- a/libraries/gpu/src/gpu/Texture_ktx.cpp
+++ b/libraries/gpu/src/gpu/Texture_ktx.cpp
@@ -184,6 +184,11 @@ KtxStorage::KtxStorage(const std::string& filename) : _filename(filename) {
 }
 
 std::shared_ptr<storage::FileStorage> KtxStorage::maybeOpenFile() const {
+    // 1. Try to get the shared ptr
+    // 2. If it doesn't exist, grab the mutex around its creation
+    // 3. If it was created before we got the mutex, return it
+    // 4. Otherwise, create it
+
     std::shared_ptr<storage::FileStorage> file = _cacheFile.lock();
     if (file) {
         return file;
@@ -205,7 +210,6 @@ std::shared_ptr<storage::FileStorage> KtxStorage::maybeOpenFile() const {
 }
 
 PixelsPointer KtxStorage::getMipFace(uint16 level, uint8 face) const {
-    storage::StoragePointer result;
     auto faceOffset = _ktxDescriptor->getMipFaceTexelsOffset(level, face);
     auto faceSize = _ktxDescriptor->getMipFaceTexelsSize(level, face);
     if (faceSize != 0 && faceOffset != 0) {
@@ -221,7 +225,7 @@ PixelsPointer KtxStorage::getMipFace(uint16 level, uint8 face) const {
             qWarning() << "Failed to get a valid file out of maybeOpenFile " << QString::fromStdString(_filename);
         }
     }
-    return result;
+    return nullptr;
 }
 
 Size KtxStorage::getMipFaceSize(uint16 level, uint8 face) const {
@@ -255,8 +259,18 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor
     }
 
     auto file = maybeOpenFile();
+    if (!file) {
+        qWarning() << "Failed to open file to assign mip data " << QString::fromStdString(_filename);
+        return;
+    }
 
-    auto imageData = file->mutableData();
+    auto fileData = file->mutableData();
+    if (!fileData) {
+        qWarning() << "Failed to get mutable data for " << QString::fromStdString(_filename);
+        return;
+    }
+
+    auto imageData = fileData;
     imageData += ktx::KTX_HEADER_SIZE + _ktxDescriptor->header.bytesOfKeyValueData + _ktxDescriptor->images[level]._imageOffset;
     imageData += ktx::IMAGE_SIZE_WIDTH;
 
@@ -271,7 +285,7 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor
         memcpy(imageData, storage->data(), storage->size());
         _minMipLevelAvailable = level;
         if (_offsetToMinMipKV > 0) {
-            auto minMipKeyData = file->mutableData() + ktx::KTX_HEADER_SIZE + _offsetToMinMipKV;
+            auto minMipKeyData = fileData + ktx::KTX_HEADER_SIZE + _offsetToMinMipKV;
             memcpy(minMipKeyData, (void*)&_minMipLevelAvailable, 1);
         }
     }
@@ -517,6 +531,8 @@ bool Texture::evalKTXFormat(const Element& mipFormat, const Element& texelFormat
         header.setCompressed(ktx::GLInternalFormat_Compressed::COMPRESSED_RED_RGTC1, ktx::GLBaseInternalFormat::RED);
     } else if (texelFormat == Format::COLOR_COMPRESSED_XY && mipFormat == Format::COLOR_COMPRESSED_XY) {
         header.setCompressed(ktx::GLInternalFormat_Compressed::COMPRESSED_RG_RGTC2, ktx::GLBaseInternalFormat::RG);
+    } else if (texelFormat == Format::COLOR_COMPRESSED_SRGBA_HIGH && mipFormat == Format::COLOR_COMPRESSED_SRGBA_HIGH) {
+        header.setCompressed(ktx::GLInternalFormat_Compressed::COMPRESSED_SRGB_ALPHA_BPTC_UNORM, ktx::GLBaseInternalFormat::RGBA);
     } else {
         return false;
     }
@@ -575,6 +591,9 @@ bool Texture::evalTextureFormat(const ktx::Header& header, Element& mipFormat, E
         } else if (header.getGLInternaFormat_Compressed() == ktx::GLInternalFormat_Compressed::COMPRESSED_RG_RGTC2) {
             mipFormat = Format::COLOR_COMPRESSED_XY;
             texelFormat = Format::COLOR_COMPRESSED_XY;
+        } else if (header.getGLInternaFormat_Compressed() == ktx::GLInternalFormat_Compressed::COMPRESSED_SRGB_ALPHA_BPTC_UNORM) {
+            mipFormat = Format::COLOR_COMPRESSED_SRGBA_HIGH;
+            texelFormat = Format::COLOR_COMPRESSED_SRGBA_HIGH;
         } else {
             return false;
         }
diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp
index 32184dfe79..7f0674b17d 100644
--- a/libraries/image/src/image/Image.cpp
+++ b/libraries/image/src/image/Image.cpp
@@ -344,7 +344,7 @@ void generateMips(gpu::Texture* texture, QImage& image, int face = -1) {
 
     nvtt::TextureType textureType = nvtt::TextureType_2D;
     nvtt::InputFormat inputFormat = nvtt::InputFormat_BGRA_8UB;
-    nvtt::WrapMode wrapMode = nvtt::WrapMode_Repeat;
+    nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror;
     nvtt::RoundMode roundMode = nvtt::RoundMode_None;
     nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None;
 
@@ -380,6 +380,9 @@ void generateMips(gpu::Texture* texture, QImage& image, int face = -1) {
         compressionOptions.setFormat(nvtt::Format_BC4);
     } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_XY) {
         compressionOptions.setFormat(nvtt::Format_BC5);
+    } else if (mipFormat == gpu::Element::COLOR_COMPRESSED_SRGBA_HIGH) {
+        alphaMode = nvtt::AlphaMode_Transparency;
+        compressionOptions.setFormat(nvtt::Format_BC7);
     } else if (mipFormat == gpu::Element::COLOR_RGBA_32) {
         compressionOptions.setFormat(nvtt::Format_RGBA);
         compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm);
@@ -934,8 +937,8 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage&
         gpu::Element formatMip;
         gpu::Element formatGPU;
         if (isCubeTexturesCompressionEnabled()) {
-            formatMip = gpu::Element::COLOR_COMPRESSED_SRGBA;
-            formatGPU = gpu::Element::COLOR_COMPRESSED_SRGBA;
+            formatMip = gpu::Element::COLOR_COMPRESSED_SRGBA_HIGH;
+            formatGPU = gpu::Element::COLOR_COMPRESSED_SRGBA_HIGH;
         } else {
             formatMip = gpu::Element::COLOR_SRGBA_32;
             formatGPU = gpu::Element::COLOR_SRGBA_32;
diff --git a/libraries/ktx/src/ktx/KTX.cpp b/libraries/ktx/src/ktx/KTX.cpp
index b43d015d65..c366daf7ed 100644
--- a/libraries/ktx/src/ktx/KTX.cpp
+++ b/libraries/ktx/src/ktx/KTX.cpp
@@ -56,6 +56,7 @@ uint32_t Header::evalPixelOrBlockHeight(uint32_t level) const {
             case GLInternalFormat_Compressed::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: // BC3
             case GLInternalFormat_Compressed::COMPRESSED_RED_RGTC1: // BC4
             case GLInternalFormat_Compressed::COMPRESSED_RG_RGTC2: // BC5
+            case GLInternalFormat_Compressed::COMPRESSED_SRGB_ALPHA_BPTC_UNORM: // BC7
                 return (pixelWidth + 3) / 4;
             default:
                 throw std::runtime_error("Unknown format");
@@ -81,6 +82,8 @@ size_t Header::evalPixelOrBlockSize() const {
             return 8;
         } else if (format == GLInternalFormat_Compressed::COMPRESSED_RG_RGTC2) {
             return 16;
+        } else if (format == GLInternalFormat_Compressed::COMPRESSED_SRGB_ALPHA_BPTC_UNORM) {
+            return 16;
         }
     } else {
         auto baseFormat = getGLBaseInternalFormat();
diff --git a/libraries/ktx/src/ktx/Writer.cpp b/libraries/ktx/src/ktx/Writer.cpp
index 2dcd0ea583..23f9d05596 100644
--- a/libraries/ktx/src/ktx/Writer.cpp
+++ b/libraries/ktx/src/ktx/Writer.cpp
@@ -149,7 +149,8 @@ namespace ktx {
 
         for (size_t i = 0; i < descriptors.size(); ++i) {
             auto ptr = reinterpret_cast<uint32_t*>(currentDestPtr);
-            *ptr = descriptors[i]._imageSize;
+            uint32_t imageFaceSize = descriptors[i]._faceSize;
+            *ptr = imageFaceSize; // the imageSize written in the ktx is the FACE size
 
 #ifdef DEBUG
             ptr++;
diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp
index 83f1504f00..471448d596 100644
--- a/libraries/networking/src/AccountManager.cpp
+++ b/libraries/networking/src/AccountManager.cpp
@@ -28,6 +28,7 @@
 
 #include <SettingHandle.h>
 
+#include "NetworkingConstants.h"
 #include "NetworkLogging.h"
 #include "NodeList.h"
 #include "udt/PacketHeaders.h"
@@ -92,6 +93,7 @@ AccountManager::AccountManager(UserAgentGetter userAgentGetter) :
 }
 
 const QString DOUBLE_SLASH_SUBSTITUTE = "slashslash";
+const QString ACCOUNT_MANAGER_REQUESTED_SCOPE = "owner";
 
 void AccountManager::logout() {
     // a logout means we want to delete the DataServerAccountInfo we currently have for this URL, in-memory and in file
@@ -189,6 +191,12 @@ void AccountManager::setAuthURL(const QUrl& authURL) {
             requestProfile();
         }
 
+        // prepare to refresh our token if it is about to expire
+        if (needsToRefreshToken()) {
+            qCDebug(networking) << "Refreshing access token since it will be expiring soon.";
+            refreshAccessToken();
+        }
+
         // tell listeners that the auth endpoint has changed
         emit authEndpointChanged();
     }
@@ -225,6 +233,10 @@ void AccountManager::sendRequest(const QString& path,
                                 uuidStringWithoutCurlyBraces(_sessionID).toLocal8Bit());
 
     QUrl requestURL = _authURL;
+    
+    if (requestURL.isEmpty()) {  // Assignment client doesn't set _authURL.
+        requestURL = NetworkingConstants::METAVERSE_SERVER_URL;
+    }
 
     if (path.startsWith("/")) {
         requestURL.setPath(path);
@@ -443,6 +455,12 @@ bool AccountManager::hasValidAccessToken() {
 
         return false;
     } else {
+
+        if (!_isWaitingForTokenRefresh && needsToRefreshToken()) {
+            qCDebug(networking) << "Refreshing access token since it will be expiring soon.";
+            refreshAccessToken();
+        }
+
         return true;
     }
 }
@@ -458,6 +476,15 @@ bool AccountManager::checkAndSignalForAccessToken() {
     return hasToken;
 }
 
+bool AccountManager::needsToRefreshToken() {
+    if (!_accountInfo.getAccessToken().token.isEmpty()) {
+        qlonglong expireThreshold = QDateTime::currentDateTime().addSecs(1 * 60 * 60).toMSecsSinceEpoch();
+        return _accountInfo.getAccessToken().expiryTimestamp < expireThreshold;
+    } else {
+        return false;
+    }
+}
+
 void AccountManager::setAccessTokenForCurrentAuthURL(const QString& accessToken) {
     // replace the account info access token with a new OAuthAccessToken
     OAuthAccessToken newOAuthToken;
@@ -490,8 +517,6 @@ void AccountManager::requestAccessToken(const QString& login, const QString& pas
     QUrl grantURL = _authURL;
     grantURL.setPath("/oauth/token");
 
-    const QString ACCOUNT_MANAGER_REQUESTED_SCOPE = "owner";
-
     QByteArray postData;
     postData.append("grant_type=password&");
     postData.append("username=" + login + "&");
@@ -515,8 +540,6 @@ void AccountManager::requestAccessTokenWithSteam(QByteArray authSessionTicket) {
     QUrl grantURL = _authURL;
     grantURL.setPath("/oauth/token");
 
-    const QString ACCOUNT_MANAGER_REQUESTED_SCOPE = "owner";
-
     QByteArray postData;
     postData.append("grant_type=password&");
     postData.append("steam_auth_ticket=" + QUrl::toPercentEncoding(authSessionTicket) + "&");
@@ -530,6 +553,32 @@ void AccountManager::requestAccessTokenWithSteam(QByteArray authSessionTicket) {
     connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError)));
 }
 
+void AccountManager::refreshAccessToken() {
+
+    _isWaitingForTokenRefresh = true;
+
+    QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
+
+    QNetworkRequest request;
+    request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
+    request.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter());
+
+    QUrl grantURL = _authURL;
+    grantURL.setPath("/oauth/token");
+
+    QByteArray postData;
+    postData.append("grant_type=refresh_token&");
+    postData.append("refresh_token=" + QUrl::toPercentEncoding(_accountInfo.getAccessToken().refreshToken) + "&");
+    postData.append("scope=" + ACCOUNT_MANAGER_REQUESTED_SCOPE);
+
+    request.setUrl(grantURL);
+    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
+
+    QNetworkReply* requestReply = networkAccessManager.post(request, postData);
+    connect(requestReply, &QNetworkReply::finished, this, &AccountManager::refreshAccessTokenFinished);
+    connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(refreshAccessTokenError(QNetworkReply::NetworkError)));
+}
+
 void AccountManager::requestAccessTokenFinished() {
     QNetworkReply* requestReply = reinterpret_cast<QNetworkReply*>(sender());
 
@@ -568,10 +617,47 @@ void AccountManager::requestAccessTokenFinished() {
 
 void AccountManager::requestAccessTokenError(QNetworkReply::NetworkError error) {
     // TODO: error handling
-    qCDebug(networking) << "AccountManager requestError - " << error;
+    qCDebug(networking) << "AccountManager: failed to fetch access token - " << error;
     emit loginFailed();
 }
 
+void AccountManager::refreshAccessTokenFinished() {
+    QNetworkReply* requestReply = reinterpret_cast<QNetworkReply*>(sender());
+
+    QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll());
+    const QJsonObject& rootObject = jsonResponse.object();
+
+    if (!rootObject.contains("error")) {
+        // construct an OAuthAccessToken from the json object
+
+        if (!rootObject.contains("access_token") || !rootObject.contains("expires_in")
+            || !rootObject.contains("token_type")) {
+            // TODO: error handling - malformed token response
+            qCDebug(networking) << "Received a response for refresh grant that is missing one or more expected values.";
+        } else {
+            // clear the path from the response URL so we have the right root URL for this access token
+            QUrl rootURL = requestReply->url();
+            rootURL.setPath("");
+
+            qCDebug(networking) << "Storing an account with a refreshed access-token for" << qPrintable(rootURL.toString());
+
+            _accountInfo.setAccessTokenFromJSON(rootObject);
+
+            persistAccountToFile();
+        }
+    } else {
+        qCWarning(networking) << "Error in response for refresh grant - " << rootObject["error_description"].toString();
+    }
+
+    _isWaitingForTokenRefresh = false;
+}
+
+void AccountManager::refreshAccessTokenError(QNetworkReply::NetworkError error) {
+    // TODO: error handling
+    qCDebug(networking) << "AccountManager: failed to refresh access token - " << error;
+    _isWaitingForTokenRefresh = false;
+}
+
 void AccountManager::requestProfile() {
     QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
 
diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h
index eb4d224501..dd2216957f 100644
--- a/libraries/networking/src/AccountManager.h
+++ b/libraries/networking/src/AccountManager.h
@@ -79,6 +79,7 @@ public:
 
     bool isLoggedIn() { return !_authURL.isEmpty() && hasValidAccessToken(); }
     bool hasValidAccessToken();
+    bool needsToRefreshToken();
     Q_INVOKABLE bool checkAndSignalForAccessToken();
     void setAccessTokenForCurrentAuthURL(const QString& accessToken);
 
@@ -97,10 +98,13 @@ public:
 public slots:
     void requestAccessToken(const QString& login, const QString& password);
     void requestAccessTokenWithSteam(QByteArray authSessionTicket);
+    void refreshAccessToken();
 
     void requestAccessTokenFinished();
+    void refreshAccessTokenFinished();
     void requestProfileFinished();
     void requestAccessTokenError(QNetworkReply::NetworkError error);
+    void refreshAccessTokenError(QNetworkReply::NetworkError error);
     void requestProfileError(QNetworkReply::NetworkError error);
     void logout();
     void generateNewUserKeypair() { generateNewKeypair(); }
@@ -141,6 +145,7 @@ private:
     QMap<QNetworkReply*, JSONCallbackParameters> _pendingCallbackMap;
 
     DataServerAccountInfo _accountInfo;
+    bool _isWaitingForTokenRefresh { false };
     bool _isAgent { false };
 
     bool _isWaitingForKeypairResponse { false };
diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp
index c66fe8daf0..8fc1d66cf1 100644
--- a/libraries/networking/src/AddressManager.cpp
+++ b/libraries/networking/src/AddressManager.cpp
@@ -30,9 +30,9 @@
 #include "udt/PacketHeaders.h"
 
 #if USE_STABLE_GLOBAL_SERVICES
-const QString DEFAULT_HIFI_ADDRESS = "hifi://welcome";
+const QString DEFAULT_HIFI_ADDRESS = "hifi://welcome/hello";
 #else
-const QString DEFAULT_HIFI_ADDRESS = "hifi://dev-welcome";
+const QString DEFAULT_HIFI_ADDRESS = "hifi://dev-welcome/hello";
 #endif
 
 const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager";
diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp
index 0c804fb5b7..452495cf18 100644
--- a/libraries/physics/src/EntityMotionState.cpp
+++ b/libraries/physics/src/EntityMotionState.cpp
@@ -185,6 +185,10 @@ PhysicsMotionType EntityMotionState::computePhysicsMotionType() const {
         return MOTION_TYPE_STATIC;
     }
 
+    if (_entity->getLocked()) {
+        return MOTION_TYPE_STATIC;
+    }
+
     if (_entity->getDynamic()) {
         if (!_entity->getParentID().isNull()) {
             // if something would have been dynamic but is a child of something else, force it to be kinematic, instead.
diff --git a/libraries/physics/src/ObjectAction.cpp b/libraries/physics/src/ObjectAction.cpp
index de14a46be4..87732ded03 100644
--- a/libraries/physics/src/ObjectAction.cpp
+++ b/libraries/physics/src/ObjectAction.cpp
@@ -58,6 +58,10 @@ void ObjectAction::updateAction(btCollisionWorld* collisionWorld, btScalar delta
         return;
     }
 
+    if (ownerEntity->getLocked()) {
+        return;
+    }
+
     updateActionWorker(deltaTimeStep);
 }
 
diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp
index 2e3d0385cd..f3ee846d39 100644
--- a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp
+++ b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp
@@ -17,7 +17,7 @@
 
 using namespace render;
 
-CauterizedMeshPartPayload::CauterizedMeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform)
+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(
@@ -29,8 +29,16 @@ void CauterizedMeshPartPayload::updateTransformForCauterizedMesh(
 
 void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const {
     // Still relying on the raw data from the model
-    CauterizedModel* skeleton = static_cast<CauterizedModel*>(_model);
-    bool useCauterizedMesh = (renderMode != RenderArgs::RenderMode::SHADOW_RENDER_MODE) && skeleton->getEnableCauterization();
+    bool useCauterizedMesh = (renderMode != RenderArgs::RenderMode::SHADOW_RENDER_MODE);
+    if (useCauterizedMesh) {
+        ModelPointer model = _model.lock();
+        if (model) {
+            CauterizedModel* skeleton = static_cast<CauterizedModel*>(model.get());
+            useCauterizedMesh = useCauterizedMesh && skeleton->getEnableCauterization();
+        } else {
+            useCauterizedMesh = false;
+        }
+    }
 
     if (useCauterizedMesh) {
         if (_cauterizedClusterBuffer) {
diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.h b/libraries/render-utils/src/CauterizedMeshPartPayload.h
index 010cd6fcb6..5e3135ea84 100644
--- a/libraries/render-utils/src/CauterizedMeshPartPayload.h
+++ b/libraries/render-utils/src/CauterizedMeshPartPayload.h
@@ -13,7 +13,7 @@
 
 class CauterizedMeshPartPayload : public ModelMeshPartPayload {
 public:
-    CauterizedMeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform);
+    CauterizedMeshPartPayload(ModelPointer model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform);
 
     void updateTransformForCauterizedMesh(const Transform& renderTransform, const gpu::BufferPointer& buffer);
 
diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp
index 14625952ea..47ada457a0 100644
--- a/libraries/render-utils/src/CauterizedModel.cpp
+++ b/libraries/render-utils/src/CauterizedModel.cpp
@@ -16,8 +16,8 @@
 #include "RenderUtilsLogging.h"
 
 
-CauterizedModel::CauterizedModel(RigPointer rig, QObject* parent) :
-        Model(rig, parent) {
+CauterizedModel::CauterizedModel(QObject* parent) :
+        Model(parent) {
 }
 
 CauterizedModel::~CauterizedModel() {
@@ -78,7 +78,7 @@ void CauterizedModel::createVisibleRenderItemSet() {
             // Create the render payloads
             int numParts = (int)mesh->getNumParts();
             for (int partIndex = 0; partIndex < numParts; partIndex++) {
-                auto ptr = std::make_shared<CauterizedMeshPartPayload>(this, i, partIndex, shapeID, transform, offset);
+                auto ptr = std::make_shared<CauterizedMeshPartPayload>(shared_from_this(), i, partIndex, shapeID, transform, offset);
                 _modelMeshRenderItems << std::static_pointer_cast<ModelMeshPartPayload>(ptr);
                 shapeID++;
             }
@@ -107,7 +107,7 @@ void CauterizedModel::updateClusterMatrices() {
         const FBXMesh& mesh = geometry.meshes.at(i);
         for (int j = 0; j < mesh.clusters.size(); j++) {
             const FBXCluster& cluster = mesh.clusters.at(j);
-            auto jointMatrix = _rig->getJointTransform(cluster.jointIndex);
+            auto jointMatrix = _rig.getJointTransform(cluster.jointIndex);
             glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
         }
 
@@ -130,14 +130,14 @@ void CauterizedModel::updateClusterMatrices() {
             glm::vec4(0.0f, 0.0f, 0.0f, 0.0f),
             glm::vec4(0.0f, 0.0f, 0.0f, 0.0f),
             glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
-        auto cauterizeMatrix = _rig->getJointTransform(geometry.neckJointIndex) * zeroScale;
+        auto cauterizeMatrix = _rig.getJointTransform(geometry.neckJointIndex) * zeroScale;
 
         for (int i = 0; i < _cauterizeMeshStates.size(); i++) {
             Model::MeshState& state = _cauterizeMeshStates[i];
             const FBXMesh& mesh = geometry.meshes.at(i);
             for (int j = 0; j < mesh.clusters.size(); j++) {
                 const FBXCluster& cluster = mesh.clusters.at(j);
-                auto jointMatrix = _rig->getJointTransform(cluster.jointIndex);
+                auto jointMatrix = _rig.getJointTransform(cluster.jointIndex);
                 if (_cauterizeBoneSet.find(cluster.jointIndex) != _cauterizeBoneSet.end()) {
                     jointMatrix = cauterizeMatrix;
                 }
@@ -207,11 +207,12 @@ void CauterizedModel::updateRenderItems() {
             QList<render::ItemID> keys = self->getRenderItems().keys();
             foreach (auto itemID, keys) {
                 transaction.updateItem<CauterizedMeshPartPayload>(itemID, [modelTransform, deleteGeometryCounter](CauterizedMeshPartPayload& data) {
-                    if (data._model && data._model->isLoaded()) {
+                    ModelPointer model = data._model.lock();
+                    if (model && model->isLoaded()) {
                         // Ensure the model geometry was not reset between frames
-                        if (deleteGeometryCounter == data._model->getGeometryCounter()) {
+                        if (deleteGeometryCounter == model->getGeometryCounter()) {
                             // this stuff identical to what happens in regular Model
-                            const Model::MeshState& state = data._model->getMeshState(data._meshIndex);
+                            const Model::MeshState& state = model->getMeshState(data._meshIndex);
                             Transform renderTransform = modelTransform;
                             if (state.clusterMatrices.size() == 1) {
                                 renderTransform = modelTransform.worldTransform(Transform(state.clusterMatrices[0]));
@@ -219,7 +220,7 @@ void CauterizedModel::updateRenderItems() {
                             data.updateTransformForSkinnedMesh(renderTransform, modelTransform, state.clusterBuffer);
 
                             // this stuff for cauterized mesh
-                            CauterizedModel* cModel = static_cast<CauterizedModel*>(data._model);
+                            CauterizedModel* cModel = static_cast<CauterizedModel*>(model.get());
                             const Model::MeshState& cState = cModel->getCauterizeMeshState(data._meshIndex);
                             renderTransform = modelTransform;
                             if (cState.clusterMatrices.size() == 1) {
diff --git a/libraries/render-utils/src/CauterizedModel.h b/libraries/render-utils/src/CauterizedModel.h
index dcff7bd12d..d16c928ba6 100644
--- a/libraries/render-utils/src/CauterizedModel.h
+++ b/libraries/render-utils/src/CauterizedModel.h
@@ -16,7 +16,7 @@ class CauterizedModel : public Model {
     Q_OBJECT
 
 public:
-    CauterizedModel(RigPointer rig, QObject* parent);
+    CauterizedModel(QObject* parent);
     virtual ~CauterizedModel();
 
     void flagAsCauterized() { _isCauterized = true; }
diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp
index 2e08420073..b4fd7e7d2a 100644
--- a/libraries/render-utils/src/MeshPartPayload.cpp
+++ b/libraries/render-utils/src/MeshPartPayload.cpp
@@ -14,7 +14,6 @@
 #include <PerfStat.h>
 
 #include "DeferredLightingEffect.h"
-#include "Model.h"
 #include "EntityItem.h"
 
 using namespace render;
@@ -321,13 +320,13 @@ template <> void payloadRender(const ModelMeshPartPayload::Pointer& payload, Ren
 }
 }
 
-ModelMeshPartPayload::ModelMeshPartPayload(Model* model, int _meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform) :
-    _model(model),
+ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int _meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform) :
     _meshIndex(_meshIndex),
     _shapeID(shapeIndex) {
 
-    assert(_model && _model->isLoaded());
-    auto& modelMesh = _model->getGeometry()->getMeshes().at(_meshIndex);
+    assert(model && model->isLoaded());
+    _model = model;
+    auto& modelMesh = model->getGeometry()->getMeshes().at(_meshIndex);
     updateMeshPart(modelMesh, partIndex);
 
     updateTransform(transform, offsetTransform);
@@ -335,20 +334,21 @@ ModelMeshPartPayload::ModelMeshPartPayload(Model* model, int _meshIndex, int par
 }
 
 void ModelMeshPartPayload::initCache() {
-    assert(_model->isLoaded());
+    ModelPointer model = _model.lock();
+    assert(model && model->isLoaded());
 
     if (_drawMesh) {
         auto vertexFormat = _drawMesh->getVertexFormat();
         _hasColorAttrib = vertexFormat->hasAttribute(gpu::Stream::COLOR);
         _isSkinned = vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT) && vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_INDEX);
 
-        const FBXGeometry& geometry = _model->getFBXGeometry();
+        const FBXGeometry& geometry = model->getFBXGeometry();
         const FBXMesh& mesh = geometry.meshes.at(_meshIndex);
 
         _isBlendShaped = !mesh.blendshapes.isEmpty();
     }
 
-    auto networkMaterial = _model->getGeometry()->getShapeMaterial(_shapeID);
+    auto networkMaterial = model->getGeometry()->getShapeMaterial(_shapeID);
     if (networkMaterial) {
         _drawMaterial = networkMaterial;
     }
@@ -370,29 +370,31 @@ ItemKey ModelMeshPartPayload::getKey() const {
     ItemKey::Builder builder;
     builder.withTypeShape();
 
-    if (!_model->isVisible()) {
-        builder.withInvisible();
-    }
+    ModelPointer model = _model.lock();
+    if (model) {
+        if (!model->isVisible()) {
+            builder.withInvisible();
+        }
 
-    if (_model->isLayeredInFront()) {
-        builder.withLayered();
-    }
+        if (model->isLayeredInFront()) {
+            builder.withLayered();
+        }
 
-    if (_isBlendShaped || _isSkinned) {
-        builder.withDeformed();
-    }
+        if (_isBlendShaped || _isSkinned) {
+            builder.withDeformed();
+        }
 
-    if (_drawMaterial) {
-        auto matKey = _drawMaterial->getKey();
-        if (matKey.isTranslucent()) {
+        if (_drawMaterial) {
+            auto matKey = _drawMaterial->getKey();
+            if (matKey.isTranslucent()) {
+                builder.withTransparent();
+            }
+        }
+
+        if (_fadeState != FADE_COMPLETE) {
             builder.withTransparent();
         }
     }
-
-    if (_fadeState != FADE_COMPLETE) {
-        builder.withTransparent();
-    }
-
     return builder.build();
 }
 
@@ -400,7 +402,8 @@ int ModelMeshPartPayload::getLayer() const {
     // MAgic number while we are defining the layering mechanism:
     const int LAYER_3D_FRONT = 1;
     const int LAYER_3D = 0;
-    if (_model->isLayeredInFront()) {
+    ModelPointer model = _model.lock();
+    if (model && model->isLayeredInFront()) {
         return LAYER_3D_FRONT;
     } else {
         return LAYER_3D;
@@ -410,15 +413,16 @@ int ModelMeshPartPayload::getLayer() const {
 ShapeKey ModelMeshPartPayload::getShapeKey() const {
 
     // guard against partially loaded meshes
-    if (!_model || !_model->isLoaded() || !_model->getGeometry()) {
+    ModelPointer model = _model.lock();
+    if (!model || !model->isLoaded() || !model->getGeometry()) {
         return ShapeKey::Builder::invalid();
     }
 
-    const FBXGeometry& geometry = _model->getFBXGeometry();
-    const auto& networkMeshes = _model->getGeometry()->getMeshes();
+    const FBXGeometry& geometry = model->getFBXGeometry();
+    const auto& networkMeshes = model->getGeometry()->getMeshes();
 
     // guard against partially loaded meshes
-    if (_meshIndex >= (int)networkMeshes.size() || _meshIndex >= (int)geometry.meshes.size() || _meshIndex >= (int)_model->_meshStates.size()) {
+    if (_meshIndex >= (int)networkMeshes.size() || _meshIndex >= (int)geometry.meshes.size() || _meshIndex >= (int)model->_meshStates.size()) {
         return ShapeKey::Builder::invalid();
     }
 
@@ -427,8 +431,8 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const {
     // if our index is ever out of range for either meshes or networkMeshes, then skip it, and set our _meshGroupsKnown
     // to false to rebuild out mesh groups.
     if (_meshIndex < 0 || _meshIndex >= (int)networkMeshes.size() || _meshIndex > geometry.meshes.size()) {
-        _model->_needsFixupInScene = true; // trigger remove/add cycle
-        _model->invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid
+        model->_needsFixupInScene = true; // trigger remove/add cycle
+        model->invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid
         return ShapeKey::Builder::invalid();
     }
 
@@ -452,7 +456,7 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const {
     bool isUnlit = drawMaterialKey.isUnlit();
 
     bool isSkinned = _isSkinned;
-    bool wireframe = _model->isWireframe();
+    bool wireframe = model->isWireframe();
 
     if (wireframe) {
         isTranslucent = hasTangents = hasSpecular = hasLightmap = isSkinned = false;
@@ -488,18 +492,22 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const {
 void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) {
     if (!_isBlendShaped) {
         batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0);
-
         batch.setInputFormat((_drawMesh->getVertexFormat()));
-
         batch.setInputStream(0, _drawMesh->getVertexStream());
     } else {
         batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0);
-
         batch.setInputFormat((_drawMesh->getVertexFormat()));
 
-        batch.setInputBuffer(0, _model->_blendedVertexBuffers[_meshIndex], 0, sizeof(glm::vec3));
-        batch.setInputBuffer(1, _model->_blendedVertexBuffers[_meshIndex], _drawMesh->getNumVertices() * sizeof(glm::vec3), sizeof(glm::vec3));
-        batch.setInputStream(2, _drawMesh->getVertexStream().makeRangedStream(2));
+        ModelPointer model = _model.lock();
+        if (model) {
+            batch.setInputBuffer(0, model->_blendedVertexBuffers[_meshIndex], 0, sizeof(glm::vec3));
+            batch.setInputBuffer(1, model->_blendedVertexBuffers[_meshIndex], _drawMesh->getNumVertices() * sizeof(glm::vec3), sizeof(glm::vec3));
+            batch.setInputStream(2, _drawMesh->getVertexStream().makeRangedStream(2));
+        } else {
+            batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0);
+            batch.setInputFormat((_drawMesh->getVertexFormat()));
+            batch.setInputStream(0, _drawMesh->getVertexStream());
+        }
     }
 
     if (_fadeState != FADE_COMPLETE) {
@@ -530,7 +538,10 @@ float ModelMeshPartPayload::computeFadeAlpha() {
     if (fadeAlpha >= 1.0f) {
         _fadeState = FADE_COMPLETE;
         // when fade-in completes we flag model for one last "render item update"
-        _model->setRenderItemsNeedUpdate();
+        ModelPointer model = _model.lock();
+        if (model) {
+            model->setRenderItemsNeedUpdate();
+        }
         return 1.0f;
     }
     return Interpolate::simpleNonLinearBlend(fadeAlpha);
@@ -539,26 +550,27 @@ float ModelMeshPartPayload::computeFadeAlpha() {
 void ModelMeshPartPayload::render(RenderArgs* args) {
     PerformanceTimer perfTimer("ModelMeshPartPayload::render");
 
-    if (!_model->addedToScene() || !_model->isVisible()) {
+    ModelPointer model = _model.lock();
+    if (!model || !model->addedToScene() || !model->isVisible()) {
         return; // bail asap
     }
 
     if (_fadeState == FADE_WAITING_TO_START) {
-        if (_model->isLoaded()) {
+        if (model->isLoaded()) {
             if (EntityItem::getEntitiesShouldFadeFunction()()) {
                 _fadeStartTime = usecTimestampNow();
                 _fadeState = FADE_IN_PROGRESS;
             } else {
                 _fadeState = FADE_COMPLETE;
             }
-            _model->setRenderItemsNeedUpdate();
+            model->setRenderItemsNeedUpdate();
         } else {
             return;
         }
     }
 
-    if (_materialNeedsUpdate && _model->getGeometry()->areTexturesLoaded()) {
-        _model->setRenderItemsNeedUpdate();
+    if (_materialNeedsUpdate && model->getGeometry()->areTexturesLoaded()) {
+        model->setRenderItemsNeedUpdate();
         _materialNeedsUpdate = false;
     }
 
diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h
index 11d1bbf6a7..5d12e60ce3 100644
--- a/libraries/render-utils/src/MeshPartPayload.h
+++ b/libraries/render-utils/src/MeshPartPayload.h
@@ -21,6 +21,8 @@
 
 #include <model/Geometry.h>
 
+#include "Model.h"
+
 const uint8_t FADE_WAITING_TO_START = 0;
 const uint8_t FADE_IN_PROGRESS = 1;
 const uint8_t FADE_COMPLETE = 2;
@@ -83,7 +85,7 @@ namespace render {
 
 class ModelMeshPartPayload : public MeshPartPayload {
 public:
-    ModelMeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform);
+    ModelMeshPartPayload(ModelPointer model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform);
 
     typedef render::Payload<ModelMeshPartPayload> Payload;
     typedef Payload::DataPointer Pointer;
@@ -110,7 +112,7 @@ public:
     void computeAdjustedLocalBound(const QVector<glm::mat4>& clusterMatrices);
 
     gpu::BufferPointer _clusterBuffer;
-    Model* _model;
+    ModelWeakPointer _model;
 
     int _meshIndex;
     int _shapeID;
diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp
index e09afa3f31..e5a25d733e 100644
--- a/libraries/render-utils/src/Model.cpp
+++ b/libraries/render-utils/src/Model.cpp
@@ -78,7 +78,7 @@ void initCollisionMaterials() {
     }
 }
 
-Model::Model(RigPointer rig, QObject* parent, SpatiallyNestable* spatiallyNestableOverride) :
+Model::Model(QObject* parent, SpatiallyNestable* spatiallyNestableOverride) :
     QObject(parent),
     _renderGeometry(),
     _collisionGeometry(),
@@ -96,8 +96,7 @@ Model::Model(RigPointer rig, QObject* parent, SpatiallyNestable* spatiallyNestab
     _isVisible(true),
     _blendNumber(0),
     _appliedBlendNumber(0),
-    _isWireframe(false),
-    _rig(rig)
+    _isWireframe(false)
 {
     // we may have been created in the network thread, but we live in the main thread
     if (_viewState) {
@@ -236,13 +235,14 @@ void Model::updateRenderItems() {
         render::Transaction transaction;
         foreach (auto itemID, self->_modelMeshRenderItemsMap.keys()) {
             transaction.updateItem<ModelMeshPartPayload>(itemID, [deleteGeometryCounter](ModelMeshPartPayload& data) {
-                if (data._model && data._model->isLoaded()) {
+                ModelPointer model = data._model.lock();
+                if (model && model->isLoaded()) {
                     // Ensure the model geometry was not reset between frames
-                    if (deleteGeometryCounter == data._model->_deleteGeometryCounter) {
-                        Transform modelTransform = data._model->getTransform();
+                    if (deleteGeometryCounter == model->_deleteGeometryCounter) {
+                        Transform modelTransform = model->getTransform();
                         modelTransform.setScale(glm::vec3(1.0f));
 
-                        const Model::MeshState& state = data._model->getMeshState(data._meshIndex);
+                        const Model::MeshState& state = model->getMeshState(data._meshIndex);
                         Transform renderTransform = modelTransform;
                         if (state.clusterMatrices.size() == 1) {
                             renderTransform = modelTransform.worldTransform(Transform(state.clusterMatrices[0]));
@@ -271,7 +271,7 @@ void Model::updateRenderItems() {
 void Model::initJointTransforms() {
     if (isLoaded()) {
         glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset);
-        _rig->setModelOffset(modelOffset);
+        _rig.setModelOffset(modelOffset);
     }
 }
 
@@ -281,7 +281,7 @@ void Model::init() {
 void Model::reset() {
     if (isLoaded()) {
         const FBXGeometry& geometry = getFBXGeometry();
-        _rig->reset(geometry);
+        _rig.reset(geometry);
     }
 }
 
@@ -294,7 +294,8 @@ bool Model::updateGeometry() {
 
     _needsReload = false;
 
-    if (_rig->jointStatesEmpty() && getFBXGeometry().joints.size() > 0) {
+    // TODO: should all Models have a valid _rig?
+    if (_rig.jointStatesEmpty() && getFBXGeometry().joints.size() > 0) {
         initJointStates();
         assert(_meshStates.empty());
 
@@ -326,7 +327,7 @@ void Model::initJointStates() {
     const FBXGeometry& geometry = getFBXGeometry();
     glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset);
 
-    _rig->initJointStates(geometry, modelOffset);
+    _rig.initJointStates(geometry, modelOffset);
 }
 
 bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance,
@@ -715,6 +716,11 @@ Extents Model::getBindExtents() const {
     return scaledExtents;
 }
 
+glm::vec3 Model::getNaturalDimensions() const {
+    Extents modelMeshExtents = getUnscaledMeshExtents();
+    return modelMeshExtents.maximum - modelMeshExtents.minimum;
+}
+
 Extents Model::getMeshExtents() const {
     if (!isActive()) {
         return Extents();
@@ -746,19 +752,19 @@ Extents Model::getUnscaledMeshExtents() const {
 }
 
 void Model::clearJointState(int index) {
-    _rig->clearJointState(index);
+    _rig.clearJointState(index);
 }
 
 void Model::setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority) {
-    _rig->setJointState(index, valid, rotation, translation, priority);
+    _rig.setJointState(index, valid, rotation, translation, priority);
 }
 
 void Model::setJointRotation(int index, bool valid, const glm::quat& rotation, float priority) {
-    _rig->setJointRotation(index, valid, rotation, priority);
+    _rig.setJointRotation(index, valid, rotation, priority);
 }
 
 void Model::setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority) {
-    _rig->setJointTranslation(index, valid, translation, priority);
+    _rig.setJointTranslation(index, valid, translation, priority);
 }
 
 int Model::getParentJointIndex(int jointIndex) const {
@@ -810,8 +816,10 @@ void Model::setURL(const QUrl& url) {
     deleteGeometry();
 
     auto resource = DependencyManager::get<ModelCache>()->getGeometryResource(url);
-    resource->setLoadPriority(this, _loadingPriority);
-    _renderWatcher.setResource(resource);
+    if (resource) {
+        resource->setLoadPriority(this, _loadingPriority);
+        _renderWatcher.setResource(resource);
+    }
     onInvalidate();
 }
 
@@ -823,43 +831,43 @@ void Model::loadURLFinished(bool success) {
 }
 
 bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const {
-    return _rig->getJointPositionInWorldFrame(jointIndex, position, _translation, _rotation);
+    return _rig.getJointPositionInWorldFrame(jointIndex, position, _translation, _rotation);
 }
 
 bool Model::getJointPosition(int jointIndex, glm::vec3& position) const {
-    return _rig->getJointPosition(jointIndex, position);
+    return _rig.getJointPosition(jointIndex, position);
 }
 
 bool Model::getJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const {
-    return _rig->getJointRotationInWorldFrame(jointIndex, rotation, _rotation);
+    return _rig.getJointRotationInWorldFrame(jointIndex, rotation, _rotation);
 }
 
 bool Model::getJointRotation(int jointIndex, glm::quat& rotation) const {
-    return _rig->getJointRotation(jointIndex, rotation);
+    return _rig.getJointRotation(jointIndex, rotation);
 }
 
 bool Model::getJointTranslation(int jointIndex, glm::vec3& translation) const {
-    return _rig->getJointTranslation(jointIndex, translation);
+    return _rig.getJointTranslation(jointIndex, translation);
 }
 
 bool Model::getAbsoluteJointRotationInRigFrame(int jointIndex, glm::quat& rotationOut) const {
-    return _rig->getAbsoluteJointRotationInRigFrame(jointIndex, rotationOut);
+    return _rig.getAbsoluteJointRotationInRigFrame(jointIndex, rotationOut);
 }
 
 bool Model::getAbsoluteJointTranslationInRigFrame(int jointIndex, glm::vec3& translationOut) const {
-    return _rig->getAbsoluteJointTranslationInRigFrame(jointIndex, translationOut);
+    return _rig.getAbsoluteJointTranslationInRigFrame(jointIndex, translationOut);
 }
 
 bool Model::getRelativeDefaultJointRotation(int jointIndex, glm::quat& rotationOut) const {
-    return _rig->getRelativeDefaultJointRotation(jointIndex, rotationOut);
+    return _rig.getRelativeDefaultJointRotation(jointIndex, rotationOut);
 }
 
 bool Model::getRelativeDefaultJointTranslation(int jointIndex, glm::vec3& translationOut) const {
-    return _rig->getRelativeDefaultJointTranslation(jointIndex, translationOut);
+    return _rig.getRelativeDefaultJointTranslation(jointIndex, translationOut);
 }
 
 bool Model::getJointCombinedRotation(int jointIndex, glm::quat& rotation) const {
-    return _rig->getJointCombinedRotation(jointIndex, rotation, _rotation);
+    return _rig.getJointCombinedRotation(jointIndex, rotation, _rotation);
 }
 
 QStringList Model::getJointNames() const {
@@ -936,8 +944,8 @@ void Blender::run() {
         Q_ARG(const QVector<glm::vec3>&, normals));
 }
 
-void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions) {
-    if (_scaleToFit != scaleToFit || _scaleToFitDimensions != dimensions) {
+void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions, bool forceRescale) {
+    if (forceRescale || _scaleToFit != scaleToFit || _scaleToFitDimensions != dimensions) {
         _scaleToFit = scaleToFit;
         _scaleToFitDimensions = dimensions;
         _scaledToFit = false; // force rescaling
@@ -1047,7 +1055,7 @@ void Model::simulate(float deltaTime, bool fullUpdate) {
 void Model::updateRig(float deltaTime, glm::mat4 parentTransform) {
     _needsUpdateClusterMatrices = true;
     glm::mat4 rigToWorldTransform = createMatFromQuatAndPos(getRotation(), getTranslation());
-    _rig->updateAnimations(deltaTime, parentTransform, rigToWorldTransform);
+    _rig.updateAnimations(deltaTime, parentTransform, rigToWorldTransform);
 }
 
 void Model::computeMeshPartLocalBounds() {
@@ -1071,7 +1079,7 @@ void Model::updateClusterMatrices() {
         const FBXMesh& mesh = geometry.meshes.at(i);
         for (int j = 0; j < mesh.clusters.size(); j++) {
             const FBXCluster& cluster = mesh.clusters.at(j);
-            auto jointMatrix = _rig->getJointTransform(cluster.jointIndex);
+            auto jointMatrix = _rig.getJointTransform(cluster.jointIndex);
             glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
         }
 
@@ -1098,19 +1106,19 @@ void Model::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm:
     const FBXGeometry& geometry = getFBXGeometry();
     const QVector<int>& freeLineage = geometry.joints.at(endIndex).freeLineage;
     glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset);
-    _rig->inverseKinematics(endIndex, targetPosition, targetRotation, priority, freeLineage, parentTransform);
+    _rig.inverseKinematics(endIndex, targetPosition, targetRotation, priority, freeLineage, parentTransform);
 }
 
 bool Model::restoreJointPosition(int jointIndex, float fraction, float priority) {
     const FBXGeometry& geometry = getFBXGeometry();
     const QVector<int>& freeLineage = geometry.joints.at(jointIndex).freeLineage;
-    return _rig->restoreJointPosition(jointIndex, fraction, priority, freeLineage);
+    return _rig.restoreJointPosition(jointIndex, fraction, priority, freeLineage);
 }
 
 float Model::getLimbLength(int jointIndex) const {
     const FBXGeometry& geometry = getFBXGeometry();
     const QVector<int>& freeLineage = geometry.joints.at(jointIndex).freeLineage;
-    return _rig->getLimbLength(jointIndex, freeLineage, _scale, geometry.joints);
+    return _rig.getLimbLength(jointIndex, freeLineage, _scale, geometry.joints);
 }
 
 bool Model::maybeStartBlender() {
@@ -1153,7 +1161,7 @@ void Model::deleteGeometry() {
     _deleteGeometryCounter++;
     _blendedVertexBuffers.clear();
     _meshStates.clear();
-    _rig->destroyAnimGraph();
+    _rig.destroyAnimGraph();
     _blendedBlendshapeCoefficients.clear();
     _renderGeometry.reset();
     _collisionGeometry.reset();
@@ -1223,7 +1231,7 @@ void Model::createVisibleRenderItemSet() {
         // Create the render payloads
         int numParts = (int)mesh->getNumParts();
         for (int partIndex = 0; partIndex < numParts; partIndex++) {
-            _modelMeshRenderItems << std::make_shared<ModelMeshPartPayload>(this, i, partIndex, shapeID, transform, offset);
+            _modelMeshRenderItems << std::make_shared<ModelMeshPartPayload>(shared_from_this(), i, partIndex, shapeID, transform, offset);
             shapeID++;
         }
     }
diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h
index 5899ccf6b5..d718145d66 100644
--- a/libraries/render-utils/src/Model.h
+++ b/libraries/render-utils/src/Model.h
@@ -68,7 +68,7 @@ public:
 
     static void setAbstractViewStateInterface(AbstractViewStateInterface* viewState) { _viewState = viewState; }
 
-    Model(RigPointer rig, QObject* parent = nullptr, SpatiallyNestable* spatiallyNestableOverride = nullptr);
+    Model(QObject* parent = nullptr, SpatiallyNestable* spatiallyNestableOverride = nullptr);
     virtual ~Model();
 
     inline ModelPointer getThisPointer() const {
@@ -122,8 +122,6 @@ public:
     void init();
     void reset();
 
-    void setScaleToFit(bool scaleToFit, const glm::vec3& dimensions);
-
     void setSnapModelToRegistrationPoint(bool snapModelToRegistrationPoint, const glm::vec3& registrationPoint);
     bool getSnapModelToRegistrationPoint() { return _snapModelToRegistrationPoint; }
 
@@ -164,6 +162,7 @@ public:
     const glm::vec3& getOffset() const { return _offset; }
 
     void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f, bool forceRescale = false);
+    void setScaleToFit(bool scaleToFit, const glm::vec3& dimensions, bool forceRescale = false);
     bool getScaleToFit() const { return _scaleToFit; } /// is scale to fit enabled
 
     void setSnapModelToCenter(bool snapModelToCenter) {
@@ -174,7 +173,7 @@ public:
     }
 
     /// Returns the number of joint states in the model.
-    int getJointStateCount() const { return (int)_rig->getJointStateCount(); }
+    int getJointStateCount() const { return (int)_rig.getJointStateCount(); }
     bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const;
     bool getJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const;
     bool getJointCombinedRotation(int jointIndex, glm::quat& rotation) const;
@@ -209,6 +208,8 @@ public:
     const glm::vec3& getTranslation() const { return _translation; }
     const glm::quat& getRotation() const { return _rotation; }
 
+    glm::vec3 getNaturalDimensions() const;
+
     Transform getTransform() const;
 
     void setScale(const glm::vec3& scale);
@@ -223,7 +224,8 @@ public:
         return ((index < 0) && (index >= _blendshapeCoefficients.size())) ? 0.0f : _blendshapeCoefficients.at(index);
      }
 
-    virtual RigPointer getRig() const { return _rig; }
+    Rig& getRig() { return _rig; }
+    const Rig& getRig() const { return _rig; }
 
     const glm::vec3& getRegistrationPoint() const { return _registrationPoint; }
 
@@ -390,7 +392,7 @@ protected:
     mutable bool _needsUpdateTextures { true };
 
     friend class ModelMeshPartPayload;
-    RigPointer _rig;
+    Rig _rig;
 
     uint32_t _deleteGeometryCounter { 0 };
 
diff --git a/libraries/render-utils/src/SoftAttachmentModel.cpp b/libraries/render-utils/src/SoftAttachmentModel.cpp
index 8fef0f8f77..f15b696006 100644
--- a/libraries/render-utils/src/SoftAttachmentModel.cpp
+++ b/libraries/render-utils/src/SoftAttachmentModel.cpp
@@ -8,11 +8,9 @@
 
 #include "SoftAttachmentModel.h"
 
-SoftAttachmentModel::SoftAttachmentModel(RigPointer rig, QObject* parent, RigPointer rigOverride) :
-    CauterizedModel(rig, parent),
+SoftAttachmentModel::SoftAttachmentModel(QObject* parent, const Rig& rigOverride) :
+    CauterizedModel(parent),
     _rigOverride(rigOverride) {
-    assert(_rig);
-    assert(_rigOverride);
 }
 
 SoftAttachmentModel::~SoftAttachmentModel() {
@@ -24,11 +22,11 @@ void SoftAttachmentModel::updateRig(float deltaTime, glm::mat4 parentTransform)
 }
 
 int SoftAttachmentModel::getJointIndexOverride(int i) const {
-    QString name = _rig->nameOfJoint(i);
+    QString name = _rig.nameOfJoint(i);
     if (name.isEmpty()) {
         return -1;
     }
-    return _rigOverride->indexOfJoint(name);
+    return _rigOverride.indexOfJoint(name);
 }
 
 // virtual
@@ -51,10 +49,10 @@ void SoftAttachmentModel::updateClusterMatrices() {
             // TODO: cache these look-ups as an optimization
             int jointIndexOverride = getJointIndexOverride(cluster.jointIndex);
             glm::mat4 jointMatrix;
-            if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride->getJointStateCount()) {
-                jointMatrix = _rigOverride->getJointTransform(jointIndexOverride);
+            if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) {
+                jointMatrix = _rigOverride.getJointTransform(jointIndexOverride);
             } else {
-                jointMatrix = _rig->getJointTransform(cluster.jointIndex);
+                jointMatrix = _rig.getJointTransform(cluster.jointIndex);
             }
             glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
         }
diff --git a/libraries/render-utils/src/SoftAttachmentModel.h b/libraries/render-utils/src/SoftAttachmentModel.h
index b66c1a289a..4335c1634e 100644
--- a/libraries/render-utils/src/SoftAttachmentModel.h
+++ b/libraries/render-utils/src/SoftAttachmentModel.h
@@ -23,7 +23,7 @@ class SoftAttachmentModel : public CauterizedModel {
     Q_OBJECT
 
 public:
-    SoftAttachmentModel(RigPointer rig, QObject* parent, RigPointer rigOverride);
+    SoftAttachmentModel(QObject* parent, const Rig& rigOverride);
     ~SoftAttachmentModel();
 
     void updateRig(float deltaTime, glm::mat4 parentTransform) override;
@@ -32,7 +32,7 @@ public:
 protected:
     int getJointIndexOverride(int i) const;
 
-    RigPointer _rigOverride;
+    const Rig& _rigOverride;
 };
 
 #endif // hifi_SoftAttachmentModel_h
diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp
index 8bbb9a3e2c..03d1eb5868 100644
--- a/libraries/script-engine/src/ScriptEngine.cpp
+++ b/libraries/script-engine/src/ScriptEngine.cpp
@@ -231,7 +231,7 @@ void ScriptEngine::disconnectNonEssentialSignals() {
     // Ensure the thread should be running, and does exist
     if (_isRunning && _isThreaded && (workerThread = thread())) {
         connect(this, &ScriptEngine::doneRunning, workerThread, &QThread::quit);
-        connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater);
+        connect(this, &QObject::destroyed, workerThread, &QObject::deleteLater);
     }
 }
 
@@ -346,7 +346,7 @@ void ScriptEngine::runInThread() {
     // disconnectNonEssentialSignals() method
     connect(workerThread, &QThread::started, this, &ScriptEngine::run);
     connect(this, &ScriptEngine::doneRunning, workerThread, &QThread::quit);
-    connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater);
+    connect(this, &QObject::destroyed, workerThread, &QObject::deleteLater);
 
     workerThread->start();
 }
@@ -397,16 +397,12 @@ void ScriptEngine::waitTillDoneRunning() {
                 }
             }
 
-            // NOTE: This will be called on the main application thread from stopAllScripts.
-            //       The application thread will need to continue to process events, because
+            // NOTE: This will be called on the main application thread (among other threads) from stopAllScripts.
+            //       The thread will need to continue to process events, because
             //       the scripts will likely need to marshall messages across to the main thread, e.g.
             //       if they access Settings or Menu in any of their shutdown code. So:
-            // Process events for the main application thread, allowing invokeMethod calls to pass between threads.
+            // Process events for this thread, allowing invokeMethod calls to pass between threads.
             QCoreApplication::processEvents();
-            // In some cases (debugging), processEvents may give the thread enough time to shut down, so recheck it.
-            if (!thread()) {
-                break;
-            }
 
             // Avoid a pure busy wait
             QThread::yieldCurrentThread();
diff --git a/libraries/shared/src/RunningMarker.cpp b/libraries/shared/src/RunningMarker.cpp
index f8aaee42df..0c1fd06df8 100644
--- a/libraries/shared/src/RunningMarker.cpp
+++ b/libraries/shared/src/RunningMarker.cpp
@@ -53,6 +53,11 @@ RunningMarker::~RunningMarker() {
     _runningMarkerThread->deleteLater();
 }
 
+bool RunningMarker::fileExists() const {
+    QFile runningMarkerFile(getFilePath());
+    return runningMarkerFile.exists();
+}
+
 void RunningMarker::writeRunningMarkerFile() {
     QFile runningMarkerFile(getFilePath());
 
@@ -69,7 +74,7 @@ void RunningMarker::deleteRunningMarkerFile() {
     }
 }
 
-QString RunningMarker::getFilePath() {
+QString RunningMarker::getFilePath() const {
     return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + _name;
 }
 
diff --git a/libraries/shared/src/RunningMarker.h b/libraries/shared/src/RunningMarker.h
index 1137dbf5fa..f9c8e72d37 100644
--- a/libraries/shared/src/RunningMarker.h
+++ b/libraries/shared/src/RunningMarker.h
@@ -25,9 +25,11 @@ public:
 
     void startRunningMarker();
 
-    QString getFilePath();
+    QString getFilePath() const;
     static QString getMarkerFilePath(QString name);
 
+    bool fileExists() const;
+
     void writeRunningMarkerFile();
     void deleteRunningMarkerFile();
 
diff --git a/libraries/shared/src/shared/Storage.cpp b/libraries/shared/src/shared/Storage.cpp
index e479559e6a..b07f896df0 100644
--- a/libraries/shared/src/shared/Storage.cpp
+++ b/libraries/shared/src/shared/Storage.cpp
@@ -70,7 +70,15 @@ StoragePointer FileStorage::create(const QString& filename, size_t size, const u
 }
 
 FileStorage::FileStorage(const QString& filename) : _file(filename) {
-    if (_file.open(QFile::ReadOnly)) {
+    bool opened = _file.open(QFile::ReadWrite);
+    if (opened) {
+        _hasWriteAccess = true;
+    } else {
+        _hasWriteAccess = false;
+        opened = _file.open(QFile::ReadOnly);
+    }
+
+    if (opened) {
         _mapped = _file.map(0, _file.size());
         if (_mapped) {
             _valid = true;
@@ -91,35 +99,4 @@ FileStorage::~FileStorage() {
     if (_file.isOpen()) {
         _file.close();
     }
-}
-
-void FileStorage::ensureWriteAccess() {
-    if (_hasWriteAccess) {
-        return;
-    }
-
-    if (_mapped) {
-        if (!_file.unmap(_mapped)) {
-            throw std::runtime_error("Unable to unmap file");
-        }
-    }
-    if (_file.isOpen()) {
-        _file.close();
-    }
-    _valid = false;
-    _mapped = nullptr;
-
-    if (_file.open(QFile::ReadWrite)) {
-        _mapped = _file.map(0, _file.size());
-        if (_mapped) {
-            _valid = true;
-            _hasWriteAccess = true;
-        } else {
-            qCWarning(storagelogging) << "Failed to map file " << _file.fileName();
-            throw std::runtime_error("Failed to map file");
-        }
-    } else {
-        qCWarning(storagelogging) << "Failed to open file " << _file.fileName();
-        throw std::runtime_error("Failed to open file");
-    }
 }
\ No newline at end of file
diff --git a/libraries/shared/src/shared/Storage.h b/libraries/shared/src/shared/Storage.h
index 4cad9fa083..d7946738cf 100644
--- a/libraries/shared/src/shared/Storage.h
+++ b/libraries/shared/src/shared/Storage.h
@@ -60,11 +60,10 @@ namespace storage {
         FileStorage& operator=(const FileStorage& other) = delete;
 
         const uint8_t* data() const override { return _mapped; }
-        uint8_t* mutableData() override { ensureWriteAccess(); return _mapped; }
+        uint8_t* mutableData() override { return _hasWriteAccess ? _mapped : nullptr; }
         size_t size() const override { return _file.size(); }
         operator bool() const override { return _valid; }
     private:
-        void ensureWriteAccess();
 
         bool _valid { false };
         bool _hasWriteAccess { false };
diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js
index f962c7b624..6c5829d64f 100644
--- a/scripts/system/html/js/SnapshotReview.js
+++ b/scripts/system/html/js/SnapshotReview.js
@@ -44,6 +44,7 @@ function showSetupComplete() {
             '<p>Snapshot location set.</p>' +
             '<p>Press the big red button to take a snap!</p>' +
         '</div>';
+    document.getElementById("snap-button").disabled = false;
 }
 function showSnapshotInstructions() {
     var snapshotImagesDiv = document.getElementById("snapshot-images");
@@ -69,7 +70,6 @@ function login() {
     }));
 }
 function clearImages() {
-    document.getElementById("snap-button").disabled = false;
     var snapshotImagesDiv = document.getElementById("snapshot-images");
     snapshotImagesDiv.classList.remove("snapshotInstructions");
     while (snapshotImagesDiv.hasChildNodes()) {
@@ -300,7 +300,7 @@ function addImage(image_data, isLoggedIn, canShare, isGifLoading, isShowingPrevi
     if (!isGifLoading) {
         appendShareBar(id, isLoggedIn, canShare, isGif, blastButtonDisabled, hifiButtonDisabled, canBlast);
     }
-    if (!isGifLoading && !isShowingPreviousImages) {
+    if (!isGifLoading || (isShowingPreviousImages && !image_data.story_id)) {
         shareForUrl(id);
     }
     if (isShowingPreviousImages && isLoggedIn && image_data.story_id) {
@@ -650,6 +650,7 @@ window.onload = function () {
                             shareForUrl("p1");
                             appendShareBar("p1", messageOptions.isLoggedIn, messageOptions.canShare, true, false, false, messageOptions.canBlast);
                             document.getElementById("p1").classList.remove("processingGif");
+                            document.getElementById("snap-button").disabled = false;
                         }
                     } else {
                         imageCount = message.image_data.length;
@@ -688,6 +689,9 @@ function takeSnapshot() {
         type: "snapshot",
         action: "takeSnapshot"
     }));
+    if (document.getElementById('stillAndGif').checked === true) {
+        document.getElementById("snap-button").disabled = true;
+    }
 }
 
 function testInBrowser(test) {
diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js
index 8b5ae3c9a7..4c661482fc 100644
--- a/scripts/system/snapshot.js
+++ b/scripts/system/snapshot.js
@@ -138,10 +138,10 @@ function onMessage(message) {
                     isLoggedIn = Account.isLoggedIn();
                     if (isLoggedIn) {
                         print('Sharing snapshot with audience "for_url":', message.data);
-                        Window.shareSnapshot(message.data, message.href || href);
+                        Window.shareSnapshot(message.data, Settings.getValue("previousSnapshotHref"));
                     } else {
                         shareAfterLogin = true;
-                        snapshotToShareAfterLogin.push({ path: message.data, href: message.href || href });
+                        snapshotToShareAfterLogin.push({ path: message.data, href: Settings.getValue("previousSnapshotHref") });
                     }
                 }
             });
@@ -349,6 +349,7 @@ function takeSnapshot() {
     // We will record snapshots based on the starting location. That could change, e.g., when recording a .gif.
     // Even the domainId could change (e.g., if the user falls into a teleporter while recording).
     href = location.href;
+    Settings.setValue("previousSnapshotHref", href);
     domainId = location.domainId;
     Settings.setValue("previousSnapshotDomainID", domainId);
 
diff --git a/server-console/src/main.js b/server-console/src/main.js
index 4ce1ccfb02..98bda9a10f 100644
--- a/server-console/src/main.js
+++ b/server-console/src/main.js
@@ -42,7 +42,7 @@ const appIcon = path.join(__dirname, '../resources/console.png');
 const DELETE_LOG_FILES_OLDER_THAN_X_SECONDS = 60 * 60 * 24 * 7; // 7 Days
 const LOG_FILE_REGEX = /(domain-server|ac-monitor|ac)-.*-std(out|err).txt/;
 
-const HOME_CONTENT_URL = "http://cdn.highfidelity.com/content-sets/home-tutorial-RC39.tar.gz";
+const HOME_CONTENT_URL = "http://cdn.highfidelity.com/content-sets/home-tutorial-RC40.tar.gz";
 
 function getBuildInfo() {
     var buildInfoPath = null;
diff --git a/tests/animation/src/RigTests.cpp b/tests/animation/src/RigTests.cpp
index e8d4c41dff..d5de9226c0 100644
--- a/tests/animation/src/RigTests.cpp
+++ b/tests/animation/src/RigTests.cpp
@@ -47,7 +47,7 @@
 #include <Rig.h>
 #include "RigTests.h"
 
-static void reportJoint(RigPointer rig, int index) { // Handy for debugging
+static void reportJoint(const Rig& rig, int index) { // Handy for debugging
     std::cout << "\n";
     std::cout << index << " " << rig->getAnimSkeleton()->getJointName(index).toUtf8().data() << "\n";
     glm::vec3 pos;
@@ -58,16 +58,16 @@ static void reportJoint(RigPointer rig, int index) { // Handy for debugging
     std::cout << " rot:" << safeEulerAngles(rot) << "\n";
     std::cout << "\n";
 }
-static void reportByName(RigPointer rig, const QString& name) {
+static void reportByName(const Rig& rig, const QString& name) {
     int jointIndex = rig->indexOfJoint(name);
     reportJoint(rig, jointIndex);
 }
-static void reportAll(RigPointer rig) {
+static void reportAll(const Rig& rig) {
     for (int i = 0; i < rig->getJointStateCount(); i++) {
         reportJoint(rig, i);
     }
 }
-static void reportSome(RigPointer rig) {
+static void reportSome(const Rig& rig) {
     QString names[] = {"Head", "Neck", "RightShoulder", "RightArm", "RightForeArm", "RightHand", "Spine2", "Spine1", "Spine", "Hips", "RightUpLeg", "RightLeg", "RightFoot", "RightToeBase", "RightToe_End"};
     for (auto name : names) {
         reportByName(rig, name);
@@ -91,8 +91,7 @@ void RigTests::initTestCase() {
 #endif
     QVERIFY((bool)geometry);
 
-    _rig = std::make_shared<Rig>();
-    _rig->initJointStates(*geometry, glm::mat4());
+    _rig.initJointStates(*geometry, glm::mat4());
     std::cout << "Rig is ready " << geometry->joints.count() << " joints " << std::endl;
     reportAll(_rig);
 }
diff --git a/tests/animation/src/RigTests.h b/tests/animation/src/RigTests.h
index 4280c0a8fa..3242c27b99 100644
--- a/tests/animation/src/RigTests.h
+++ b/tests/animation/src/RigTests.h
@@ -43,13 +43,13 @@
 
 class RigTests : public QObject {
     Q_OBJECT
-    
+
  private slots:
     void initTestCase();
     void initialPoseArmsDown();
 
  private:
-    RigPointer _rig;
+    Rig _rig;
 };
 
 #endif // hifi_RigTests_h