diff --git a/BUILD.md b/BUILD.md
index b8bc1cd14c..674f0d24cc 100644
--- a/BUILD.md
+++ b/BUILD.md
@@ -63,7 +63,7 @@ If `libgnutls28-dev` 3.2.12 or higher is available via your package manager, it
 [Homebrew](http://brew.sh/) is an excellent package manager for OS X. It makes install of all hifi dependencies very simple.
 
     brew tap highfidelity/homebrew-formulas
-    brew install cmake glm zlib gnutls
+    brew install cmake glm gnutls
     brew install highfidelity/formulas/qt5
     brew link qt5 --force
     brew install highfidelity/formulas/qxmpp
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a399e11168..b7fa55d4a2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -13,7 +13,7 @@ if (WIN32)
 elseif (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
   #SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -pedantic")
   #SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-unknown-pragmas")
-  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fno-strict-aliasing")
 endif(WIN32)
 
 if (NOT QT_CMAKE_PREFIX_PATH)
diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp
index fcc2288356..5720ecaaf5 100644
--- a/assignment-client/src/Agent.cpp
+++ b/assignment-client/src/Agent.cpp
@@ -147,6 +147,15 @@ void Agent::readPendingDatagrams() {
                 }
 
             } else if (datagramPacketType == PacketTypeMixedAudio) {
+
+                QUuid senderUUID = uuidFromPacketHeader(receivedPacket);
+
+                // parse sequence number for this packet
+                int numBytesPacketHeader = numBytesForPacketHeader(receivedPacket);
+                const char* sequenceAt = receivedPacket.constData() + numBytesPacketHeader;
+                quint16 sequence = *(reinterpret_cast<const quint16*>(sequenceAt));
+                _incomingMixedAudioSequenceNumberStats.sequenceNumberReceived(sequence, senderUUID);
+
                 // parse the data and grab the average loudness
                 _receivedAudioBuffer.parseData(receivedPacket);
                 
@@ -213,8 +222,6 @@ void Agent::run() {
     
     loop.exec();
     
-    
-    
     // let the AvatarData and ResourceCache classes use our QNetworkAccessManager
     AvatarData::setNetworkAccessManager(networkManager);
     ResourceCache::setNetworkAccessManager(networkManager);
diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h
index 9af95e757c..ec8f7c88cb 100644
--- a/assignment-client/src/Agent.h
+++ b/assignment-client/src/Agent.h
@@ -71,6 +71,8 @@ private:
     ModelTreeHeadlessViewer _modelViewer;
     
     MixedAudioRingBuffer _receivedAudioBuffer;
+    SequenceNumberStats _incomingMixedAudioSequenceNumberStats;
+
     AvatarHashMap _avatarHashMap;
 };
 
diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp
index 2dc51b44a0..663aef81a7 100644
--- a/assignment-client/src/audio/AudioMixer.cpp
+++ b/assignment-client/src/audio/AudioMixer.cpp
@@ -38,6 +38,9 @@
 #include <QtCore/QJsonObject>
 #include <QtCore/QJsonValue>
 #include <QtCore/QTimer>
+#include <QtNetwork/QNetworkAccessManager>
+#include <QtNetwork/QNetworkRequest>
+#include <QtNetwork/QNetworkReply>
 
 #include <Logging.h>
 #include <NodeList.h>
@@ -54,9 +57,6 @@
 
 #include "AudioMixer.h"
 
-const short JITTER_BUFFER_MSECS = 12;
-const short JITTER_BUFFER_SAMPLES = JITTER_BUFFER_MSECS * (SAMPLE_RATE / 1000.0);
-
 const float LOUDNESS_TO_DISTANCE_RATIO = 0.00001f;
 
 const QString AUDIO_MIXER_LOGGING_TARGET_NAME = "audio-mixer";
@@ -67,6 +67,8 @@ void attachNewBufferToNode(Node *newNode) {
     }
 }
 
+bool AudioMixer::_useDynamicJitterBuffers = false;
+
 AudioMixer::AudioMixer(const QByteArray& packet) :
     ThreadedAssignment(packet),
     _trailingSleepRatio(1.0f),
@@ -76,7 +78,8 @@ AudioMixer::AudioMixer(const QByteArray& packet) :
     _sumListeners(0),
     _sumMixes(0),
     _sourceUnattenuatedZone(NULL),
-    _listenerUnattenuatedZone(NULL)
+    _listenerUnattenuatedZone(NULL),
+    _lastSendAudioStreamStatsTime(usecTimestampNow())
 {
     
 }
@@ -427,12 +430,46 @@ void AudioMixer::sendStatsPacket() {
     } else {
         statsObject["average_mixes_per_listener"] = 0.0;
     }
-    
+
     ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject);
-    
     _sumListeners = 0;
     _sumMixes = 0;
     _numStatFrames = 0;
+
+
+    // NOTE: These stats can be too large to fit in an MTU, so we break it up into multiple packts...
+    QJsonObject statsObject2;
+
+    // add stats for each listerner
+    bool somethingToSend = false;
+    int sizeOfStats = 0;
+    int TOO_BIG_FOR_MTU = 1200; // some extra space for JSONification
+    
+    NodeList* nodeList = NodeList::getInstance();
+    int clientNumber = 0;
+    foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
+        clientNumber++;
+        AudioMixerClientData* clientData = static_cast<AudioMixerClientData*>(node->getLinkedData());
+        if (clientData) {
+            QString property = "jitterStats." + node->getUUID().toString();
+            QString value = clientData->getAudioStreamStatsString();
+            statsObject2[qPrintable(property)] = value;
+            somethingToSend = true;
+            sizeOfStats += property.size() + value.size();
+        }
+        
+        // if we're too large, send the packet
+        if (sizeOfStats > TOO_BIG_FOR_MTU) {
+            nodeList->sendStatsToDomainServer(statsObject2);
+            sizeOfStats = 0;
+            statsObject2 = QJsonObject(); // clear it
+            somethingToSend = false;
+        }
+    }
+
+    if (somethingToSend) {
+        nodeList->sendStatsToDomainServer(statsObject2);
+    }
 }
 
 void AudioMixer::run() {
@@ -445,37 +482,88 @@ void AudioMixer::run() {
 
     nodeList->linkedDataCreateCallback = attachNewBufferToNode;
     
-    // check the payload to see if we have any unattenuated zones
-    const QString UNATTENUATED_ZONE_REGEX_STRING = "--unattenuated-zone ([\\d.,-]+)";
-    QRegExp unattenuatedZoneMatch(UNATTENUATED_ZONE_REGEX_STRING);
+    // setup a QNetworkAccessManager to ask the domain-server for our settings
+    QNetworkAccessManager *networkManager = new QNetworkAccessManager(this);
     
-    if (unattenuatedZoneMatch.indexIn(_payload) != -1) {
-        QString unattenuatedZoneString = unattenuatedZoneMatch.cap(1);
-        QStringList zoneStringList = unattenuatedZoneString.split(',');
+    QUrl settingsJSONURL;
+    settingsJSONURL.setScheme("http");
+    settingsJSONURL.setHost(nodeList->getDomainHandler().getHostname());
+    settingsJSONURL.setPort(DOMAIN_SERVER_HTTP_PORT);
+    settingsJSONURL.setPath("/settings.json");
+    settingsJSONURL.setQuery(QString("type=%1").arg(_type));
+    
+    QNetworkReply *reply = NULL;
+    
+    int failedAttempts = 0;
+    const int MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS = 5;
+    
+    qDebug() << "Requesting settings for assignment from domain-server at" << settingsJSONURL.toString();
+    
+    while (!reply || reply->error() != QNetworkReply::NoError) {
+        reply = networkManager->get(QNetworkRequest(settingsJSONURL));
         
-        glm::vec3 sourceCorner(zoneStringList[0].toFloat(), zoneStringList[1].toFloat(), zoneStringList[2].toFloat());
-        glm::vec3 sourceDimensions(zoneStringList[3].toFloat(), zoneStringList[4].toFloat(), zoneStringList[5].toFloat());
-     
-        glm::vec3 listenerCorner(zoneStringList[6].toFloat(), zoneStringList[7].toFloat(), zoneStringList[8].toFloat());
-        glm::vec3 listenerDimensions(zoneStringList[9].toFloat(), zoneStringList[10].toFloat(), zoneStringList[11].toFloat());
+        QEventLoop loop;
+        QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
         
-        _sourceUnattenuatedZone = new AABox(sourceCorner, sourceDimensions);
-        _listenerUnattenuatedZone = new AABox(listenerCorner, listenerDimensions);
+        loop.exec();
         
-        glm::vec3 sourceCenter = _sourceUnattenuatedZone->calcCenter();
-        glm::vec3 destinationCenter = _listenerUnattenuatedZone->calcCenter();
+        ++failedAttempts;
         
-        qDebug() << "There is an unattenuated zone with source center at"
-            << QString("%1, %2, %3").arg(sourceCenter.x).arg(sourceCenter.y).arg(sourceCenter.z);
-        qDebug() << "Buffers inside this zone will not be attenuated inside a box with center at"
-            << QString("%1, %2, %3").arg(destinationCenter.x).arg(destinationCenter.y).arg(destinationCenter.z);
+        if (failedAttempts == MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS) {
+            qDebug() << "Failed to get settings from domain-server. Bailing on assignment.";
+            setFinished(true);
+            return;
+        }
     }
-
+    
+    QJsonObject settingsObject = QJsonDocument::fromJson(reply->readAll()).object();
+    
+    // check the settings object to see if we have anything we can parse out
+    const QString AUDIO_GROUP_KEY = "audio";
+    
+    if (settingsObject.contains(AUDIO_GROUP_KEY)) {
+        QJsonObject audioGroupObject = settingsObject[AUDIO_GROUP_KEY].toObject();
+        
+        const QString UNATTENUATED_ZONE_KEY = "unattenuated-zone";
+        
+        QString unattenuatedZoneString = audioGroupObject[UNATTENUATED_ZONE_KEY].toString();
+        if (!unattenuatedZoneString.isEmpty()) {
+            QStringList zoneStringList = unattenuatedZoneString.split(',');
+            
+            glm::vec3 sourceCorner(zoneStringList[0].toFloat(), zoneStringList[1].toFloat(), zoneStringList[2].toFloat());
+            glm::vec3 sourceDimensions(zoneStringList[3].toFloat(), zoneStringList[4].toFloat(), zoneStringList[5].toFloat());
+            
+            glm::vec3 listenerCorner(zoneStringList[6].toFloat(), zoneStringList[7].toFloat(), zoneStringList[8].toFloat());
+            glm::vec3 listenerDimensions(zoneStringList[9].toFloat(), zoneStringList[10].toFloat(), zoneStringList[11].toFloat());
+            
+            _sourceUnattenuatedZone = new AABox(sourceCorner, sourceDimensions);
+            _listenerUnattenuatedZone = new AABox(listenerCorner, listenerDimensions);
+            
+            glm::vec3 sourceCenter = _sourceUnattenuatedZone->calcCenter();
+            glm::vec3 destinationCenter = _listenerUnattenuatedZone->calcCenter();
+            
+            qDebug() << "There is an unattenuated zone with source center at"
+            << QString("%1, %2, %3").arg(sourceCenter.x).arg(sourceCenter.y).arg(sourceCenter.z);
+            qDebug() << "Buffers inside this zone will not be attenuated inside a box with center at"
+            << QString("%1, %2, %3").arg(destinationCenter.x).arg(destinationCenter.y).arg(destinationCenter.z);
+        }
+        
+        // check the payload to see if we have asked for dynamicJitterBuffer support
+        const QString DYNAMIC_JITTER_BUFFER_JSON_KEY = "dynamic-jitter-buffer";
+        bool shouldUseDynamicJitterBuffers = audioGroupObject[DYNAMIC_JITTER_BUFFER_JSON_KEY].toBool();
+        if (shouldUseDynamicJitterBuffers) {
+            qDebug() << "Enable dynamic jitter buffers.";
+            _useDynamicJitterBuffers = true;
+        } else {
+            qDebug() << "Dynamic jitter buffers disabled, using old behavior.";
+        }
+    }
+    
     int nextFrame = 0;
     QElapsedTimer timer;
     timer.start();
     
-    char* clientMixBuffer = new char[NETWORK_BUFFER_LENGTH_BYTES_STEREO
+    char* clientMixBuffer = new char[NETWORK_BUFFER_LENGTH_BYTES_STEREO + sizeof(quint16)
                                      + numBytesForPacketHeaderGivenPacketType(PacketTypeMixedAudio)];
     
     int usecToSleep = BUFFER_SEND_INTERVAL_USECS;
@@ -487,8 +575,7 @@ void AudioMixer::run() {
         
         foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
             if (node->getLinkedData()) {
-                ((AudioMixerClientData*) node->getLinkedData())->checkBuffersBeforeFrameSend(JITTER_BUFFER_SAMPLES,
-                                                                                             _sourceUnattenuatedZone,
+                ((AudioMixerClientData*) node->getLinkedData())->checkBuffersBeforeFrameSend(_sourceUnattenuatedZone,
                                                                                              _listenerUnattenuatedZone);
             }
         }
@@ -545,20 +632,50 @@ void AudioMixer::run() {
             ++framesSinceCutoffEvent;
         }
         
+
+        const quint64 TOO_LONG_SINCE_LAST_SEND_AUDIO_STREAM_STATS = 1 * USECS_PER_SECOND;
+        
+        bool sendAudioStreamStats = false;
+        quint64 now = usecTimestampNow();
+        if (now - _lastSendAudioStreamStatsTime > TOO_LONG_SINCE_LAST_SEND_AUDIO_STREAM_STATS) {
+            _lastSendAudioStreamStatsTime = now;
+            sendAudioStreamStats = true;
+        }
+
         foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
             if (node->getType() == NodeType::Agent && node->getActiveSocket() && node->getLinkedData()
                 && ((AudioMixerClientData*) node->getLinkedData())->getAvatarAudioRingBuffer()) {
+
+                AudioMixerClientData* nodeData = (AudioMixerClientData*)node->getLinkedData();
+
                 prepareMixForListeningNode(node.data());
                 
+                // pack header
                 int numBytesPacketHeader = populatePacketHeader(clientMixBuffer, PacketTypeMixedAudio);
+                char* dataAt = clientMixBuffer + numBytesPacketHeader;
 
-                memcpy(clientMixBuffer + numBytesPacketHeader, _clientSamples, NETWORK_BUFFER_LENGTH_BYTES_STEREO);
-                nodeList->writeDatagram(clientMixBuffer, NETWORK_BUFFER_LENGTH_BYTES_STEREO + numBytesPacketHeader, node);
+                // pack sequence number
+                quint16 sequence = nodeData->getOutgoingSequenceNumber();
+                memcpy(dataAt, &sequence, sizeof(quint16));
+                dataAt += sizeof(quint16);
+
+                // pack mixed audio samples
+                memcpy(dataAt, _clientSamples, NETWORK_BUFFER_LENGTH_BYTES_STEREO);
+                dataAt += NETWORK_BUFFER_LENGTH_BYTES_STEREO;
+
+                // send mixed audio packet
+                nodeList->writeDatagram(clientMixBuffer, dataAt - clientMixBuffer, node);
+                nodeData->incrementOutgoingMixedAudioSequenceNumber();
                 
+                // send an audio stream stats packet if it's time
+                if (sendAudioStreamStats) {
+                    nodeData->sendAudioStreamStatsPackets(node);
+                }
+
                 ++_sumListeners;
             }
         }
-
+        
         // push forward the next output pointers for any audio buffers we used
         foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
             if (node->getLinkedData()) {
diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h
index 39f8cf63ae..2c94f32edc 100644
--- a/assignment-client/src/audio/AudioMixer.h
+++ b/assignment-client/src/audio/AudioMixer.h
@@ -34,6 +34,9 @@ public slots:
     void readPendingDatagrams();
     
     void sendStatsPacket();
+
+    static bool getUseDynamicJitterBuffers() { return _useDynamicJitterBuffers; }
+
 private:
     /// adds one buffer to the mix for a listening node
     void addBufferToMixForListeningNodeWithBuffer(PositionalAudioRingBuffer* bufferToAdd,
@@ -54,6 +57,9 @@ private:
     int _sumMixes;
     AABox* _sourceUnattenuatedZone;
     AABox* _listenerUnattenuatedZone;
+    static bool _useDynamicJitterBuffers;
+
+    quint64 _lastSendAudioStreamStatsTime;
 };
 
 #endif // hifi_AudioMixer_h
diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp
index 2f78a4ac78..9b14ecfd19 100644
--- a/assignment-client/src/audio/AudioMixerClientData.cpp
+++ b/assignment-client/src/audio/AudioMixerClientData.cpp
@@ -16,10 +16,13 @@
 
 #include "InjectedAudioRingBuffer.h"
 
+#include "AudioMixer.h"
 #include "AudioMixerClientData.h"
 
 AudioMixerClientData::AudioMixerClientData() :
-    _ringBuffers()
+    _ringBuffers(),
+    _outgoingMixedAudioSequenceNumber(0),
+    _incomingAvatarAudioSequenceNumberStats()
 {
     
 }
@@ -43,16 +46,24 @@ AvatarAudioRingBuffer* AudioMixerClientData::getAvatarAudioRingBuffer() const {
 }
 
 int AudioMixerClientData::parseData(const QByteArray& packet) {
+
+    // parse sequence number for this packet
+    int numBytesPacketHeader = numBytesForPacketHeader(packet);
+    const char* sequenceAt = packet.constData() + numBytesPacketHeader;
+    quint16 sequence = *(reinterpret_cast<const quint16*>(sequenceAt));
+
     PacketType packetType = packetTypeForPacket(packet);
     if (packetType == PacketTypeMicrophoneAudioWithEcho
         || packetType == PacketTypeMicrophoneAudioNoEcho
         || packetType == PacketTypeSilentAudioFrame) {
 
+        _incomingAvatarAudioSequenceNumberStats.sequenceNumberReceived(sequence);
+
         // grab the AvatarAudioRingBuffer from the vector (or create it if it doesn't exist)
         AvatarAudioRingBuffer* avatarRingBuffer = getAvatarAudioRingBuffer();
         
         // read the first byte after the header to see if this is a stereo or mono buffer
-        quint8 channelFlag = packet.at(numBytesForPacketHeader(packet));
+        quint8 channelFlag = packet.at(numBytesForPacketHeader(packet) + sizeof(quint16));
         bool isStereo = channelFlag == 1;
         
         if (avatarRingBuffer && avatarRingBuffer->isStereo() != isStereo) {
@@ -65,7 +76,7 @@ int AudioMixerClientData::parseData(const QByteArray& packet) {
 
         if (!avatarRingBuffer) {
             // we don't have an AvatarAudioRingBuffer yet, so add it
-            avatarRingBuffer = new AvatarAudioRingBuffer(isStereo);
+            avatarRingBuffer = new AvatarAudioRingBuffer(isStereo, AudioMixer::getUseDynamicJitterBuffers());
             _ringBuffers.push_back(avatarRingBuffer);
         }
 
@@ -75,7 +86,9 @@ int AudioMixerClientData::parseData(const QByteArray& packet) {
         // this is injected audio
 
         // grab the stream identifier for this injected audio
-        QUuid streamIdentifier = QUuid::fromRfc4122(packet.mid(numBytesForPacketHeader(packet), NUM_BYTES_RFC4122_UUID));
+        QUuid streamIdentifier = QUuid::fromRfc4122(packet.mid(numBytesForPacketHeader(packet) + sizeof(quint16), NUM_BYTES_RFC4122_UUID));
+
+        _incomingInjectedAudioSequenceNumberStatsMap[streamIdentifier].sequenceNumberReceived(sequence);
 
         InjectedAudioRingBuffer* matchingInjectedRingBuffer = NULL;
 
@@ -88,7 +101,8 @@ int AudioMixerClientData::parseData(const QByteArray& packet) {
 
         if (!matchingInjectedRingBuffer) {
             // we don't have a matching injected audio ring buffer, so add it
-            matchingInjectedRingBuffer = new InjectedAudioRingBuffer(streamIdentifier);
+            matchingInjectedRingBuffer = new InjectedAudioRingBuffer(streamIdentifier, 
+                                                    AudioMixer::getUseDynamicJitterBuffers());
             _ringBuffers.push_back(matchingInjectedRingBuffer);
         }
 
@@ -98,10 +112,9 @@ int AudioMixerClientData::parseData(const QByteArray& packet) {
     return 0;
 }
 
-void AudioMixerClientData::checkBuffersBeforeFrameSend(int jitterBufferLengthSamples,
-                                                       AABox* checkSourceZone, AABox* listenerZone) {
+void AudioMixerClientData::checkBuffersBeforeFrameSend(AABox* checkSourceZone, AABox* listenerZone) {
     for (int i = 0; i < _ringBuffers.size(); i++) {
-        if (_ringBuffers[i]->shouldBeAddedToMix(jitterBufferLengthSamples)) {
+        if (_ringBuffers[i]->shouldBeAddedToMix()) {
             // this is a ring buffer that is ready to go
             // set its flag so we know to push its buffer when all is said and done
             _ringBuffers[i]->setWillBeAddedToMix(true);
@@ -120,20 +133,146 @@ void AudioMixerClientData::checkBuffersBeforeFrameSend(int jitterBufferLengthSam
 }
 
 void AudioMixerClientData::pushBuffersAfterFrameSend() {
-    for (int i = 0; i < _ringBuffers.size(); i++) {
+
+    QList<PositionalAudioRingBuffer*>::iterator i = _ringBuffers.begin();
+    while (i != _ringBuffers.end()) {
         // this was a used buffer, push the output pointer forwards
-        PositionalAudioRingBuffer* audioBuffer = _ringBuffers[i];
+        PositionalAudioRingBuffer* audioBuffer = *i;
 
         if (audioBuffer->willBeAddedToMix()) {
-            audioBuffer->shiftReadPosition(audioBuffer->isStereo()
-                                           ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
-
+            audioBuffer->shiftReadPosition(audioBuffer->getSamplesPerFrame());
             audioBuffer->setWillBeAddedToMix(false);
         } else if (audioBuffer->getType() == PositionalAudioRingBuffer::Injector
                    && audioBuffer->hasStarted() && audioBuffer->isStarved()) {
             // this is an empty audio buffer that has starved, safe to delete
+            // also delete its sequence number stats
+            QUuid streamIdentifier = ((InjectedAudioRingBuffer*)audioBuffer)->getStreamIdentifier();
+            _incomingInjectedAudioSequenceNumberStatsMap.remove(streamIdentifier);
             delete audioBuffer;
-            _ringBuffers.erase(_ringBuffers.begin() + i);
+            i = _ringBuffers.erase(i);
+            continue;
         }
+        i++;
     }
 }
+
+AudioStreamStats AudioMixerClientData::getAudioStreamStatsOfStream(const PositionalAudioRingBuffer* ringBuffer) const {
+    AudioStreamStats streamStats;
+    SequenceNumberStats streamSequenceNumberStats;
+
+    streamStats._streamType = ringBuffer->getType();
+    if (streamStats._streamType == PositionalAudioRingBuffer::Injector) {
+        streamStats._streamIdentifier = ((InjectedAudioRingBuffer*)ringBuffer)->getStreamIdentifier();
+        streamSequenceNumberStats = _incomingInjectedAudioSequenceNumberStatsMap.value(streamStats._streamIdentifier);
+    } else {
+        streamSequenceNumberStats = _incomingAvatarAudioSequenceNumberStats;
+    }
+    streamStats._jitterBufferFrames = ringBuffer->getCurrentJitterBufferFrames();
+    
+    streamStats._packetsReceived = streamSequenceNumberStats.getNumReceived();
+    streamStats._packetsUnreasonable = streamSequenceNumberStats.getNumUnreasonable();
+    streamStats._packetsEarly = streamSequenceNumberStats.getNumEarly();
+    streamStats._packetsLate = streamSequenceNumberStats.getNumLate();
+    streamStats._packetsLost = streamSequenceNumberStats.getNumLost();
+    streamStats._packetsRecovered = streamSequenceNumberStats.getNumRecovered();
+    streamStats._packetsDuplicate = streamSequenceNumberStats.getNumDuplicate();
+
+    return streamStats;
+}
+
+void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode) const {
+    
+    char packet[MAX_PACKET_SIZE];
+    NodeList* nodeList = NodeList::getInstance();
+
+    // The append flag is a boolean value that will be packed right after the header.  The first packet sent 
+    // inside this method will have 0 for this flag, while every subsequent packet will have 1 for this flag.
+    // The sole purpose of this flag is so the client can clear its map of injected audio stream stats when
+    // it receives a packet with an appendFlag of 0. This prevents the buildup of dead audio stream stats in the client.
+    quint8 appendFlag = 0;
+
+    // pack header
+    int numBytesPacketHeader = populatePacketHeader(packet, PacketTypeAudioStreamStats);
+    char* headerEndAt = packet + numBytesPacketHeader;
+
+    // calculate how many stream stat structs we can fit in each packet
+    const int numStreamStatsRoomFor = (MAX_PACKET_SIZE - numBytesPacketHeader - sizeof(quint8) - sizeof(quint16)) / sizeof(AudioStreamStats);
+
+    // pack and send stream stats packets until all ring buffers' stats are sent
+    int numStreamStatsRemaining = _ringBuffers.size();
+    QList<PositionalAudioRingBuffer*>::ConstIterator ringBuffersIterator = _ringBuffers.constBegin();
+    while (numStreamStatsRemaining > 0) {
+
+        char* dataAt = headerEndAt;
+
+        // pack the append flag
+        memcpy(dataAt, &appendFlag, sizeof(quint8));
+        appendFlag = 1;
+        dataAt += sizeof(quint8);
+
+        // calculate and pack the number of stream stats to follow
+        quint16 numStreamStatsToPack = std::min(numStreamStatsRemaining, numStreamStatsRoomFor);
+        memcpy(dataAt, &numStreamStatsToPack, sizeof(quint16));
+        dataAt += sizeof(quint16);
+
+        // pack the calculated number of stream stats
+        for (int i = 0; i < numStreamStatsToPack; i++) {
+            AudioStreamStats streamStats = getAudioStreamStatsOfStream(*ringBuffersIterator);
+            memcpy(dataAt, &streamStats, sizeof(AudioStreamStats));
+            dataAt += sizeof(AudioStreamStats);
+
+            ringBuffersIterator++;
+        }
+        numStreamStatsRemaining -= numStreamStatsToPack;
+
+        // send the current packet
+        nodeList->writeDatagram(packet, dataAt - packet, destinationNode);
+    }
+}
+
+QString AudioMixerClientData::getAudioStreamStatsString() const {
+    QString result;
+    AvatarAudioRingBuffer* avatarRingBuffer = getAvatarAudioRingBuffer();
+    if (avatarRingBuffer) {
+        int desiredJitterBuffer = avatarRingBuffer->getDesiredJitterBufferFrames();
+        int calculatedJitterBuffer = avatarRingBuffer->getCalculatedDesiredJitterBufferFrames();
+        int currentJitterBuffer = avatarRingBuffer->getCurrentJitterBufferFrames();
+        int overflowCount = avatarRingBuffer->getOverflowCount();
+        int samplesAvailable = avatarRingBuffer->samplesAvailable();
+        int framesAvailable = (samplesAvailable / avatarRingBuffer->getSamplesPerFrame());
+        AudioStreamStats streamStats = getAudioStreamStatsOfStream(avatarRingBuffer);
+        result += "mic.desired:" + QString::number(desiredJitterBuffer)
+            + " calculated:" + QString::number(calculatedJitterBuffer)
+            + " current:" + QString::number(currentJitterBuffer)
+            + " available:" + QString::number(framesAvailable)
+            + " samples:" + QString::number(samplesAvailable)
+            + " overflows:" + QString::number(overflowCount)
+            + " early:" + QString::number(streamStats._packetsEarly)
+            + " late:" + QString::number(streamStats._packetsLate)
+            + " lost:" + QString::number(streamStats._packetsLost);
+    } else {
+        result = "mic unknown";
+    }
+    
+    for (int i = 0; i < _ringBuffers.size(); i++) {
+        if (_ringBuffers[i]->getType() == PositionalAudioRingBuffer::Injector) {
+            int desiredJitterBuffer = _ringBuffers[i]->getDesiredJitterBufferFrames();
+            int calculatedJitterBuffer = _ringBuffers[i]->getCalculatedDesiredJitterBufferFrames();
+            int currentJitterBuffer = _ringBuffers[i]->getCurrentJitterBufferFrames();
+            int overflowCount = _ringBuffers[i]->getOverflowCount();
+            int samplesAvailable = _ringBuffers[i]->samplesAvailable();
+            int framesAvailable = (samplesAvailable / _ringBuffers[i]->getSamplesPerFrame());
+            AudioStreamStats streamStats = getAudioStreamStatsOfStream(_ringBuffers[i]);
+            result += "| injected[" + QString::number(i) + "].desired:" + QString::number(desiredJitterBuffer)
+                + " calculated:" + QString::number(calculatedJitterBuffer)
+                + " current:" + QString::number(currentJitterBuffer)
+                + " available:" + QString::number(framesAvailable)
+                + " samples:" + QString::number(samplesAvailable)
+                + " overflows:" + QString::number(overflowCount)
+                + " early:" + QString::number(streamStats._packetsEarly)
+                + " late:" + QString::number(streamStats._packetsLate)
+                + " lost:" + QString::number(streamStats._packetsLost);
+        }
+    }
+    return result;
+}
diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h
index e52b09e134..65fd4b3da3 100644
--- a/assignment-client/src/audio/AudioMixerClientData.h
+++ b/assignment-client/src/audio/AudioMixerClientData.h
@@ -17,6 +17,8 @@
 #include <PositionalAudioRingBuffer.h>
 
 #include "AvatarAudioRingBuffer.h"
+#include "AudioStreamStats.h"
+#include "SequenceNumberStats.h"
 
 class AudioMixerClientData : public NodeData {
 public:
@@ -27,11 +29,23 @@ public:
     AvatarAudioRingBuffer* getAvatarAudioRingBuffer() const;
     
     int parseData(const QByteArray& packet);
-    void checkBuffersBeforeFrameSend(int jitterBufferLengthSamples,
-                                     AABox* checkSourceZone = NULL, AABox* listenerZone = NULL);
+    void checkBuffersBeforeFrameSend(AABox* checkSourceZone = NULL, AABox* listenerZone = NULL);
     void pushBuffersAfterFrameSend();
+
+    AudioStreamStats getAudioStreamStatsOfStream(const PositionalAudioRingBuffer* ringBuffer) const;
+    QString getAudioStreamStatsString() const;
+    
+    void sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode) const;
+    
+    void incrementOutgoingMixedAudioSequenceNumber() { _outgoingMixedAudioSequenceNumber++; }
+    quint16 getOutgoingSequenceNumber() const { return _outgoingMixedAudioSequenceNumber; }
+
 private:
     QList<PositionalAudioRingBuffer*> _ringBuffers;
+
+    quint16 _outgoingMixedAudioSequenceNumber;
+    SequenceNumberStats _incomingAvatarAudioSequenceNumberStats;
+    QHash<QUuid, SequenceNumberStats> _incomingInjectedAudioSequenceNumberStatsMap;
 };
 
 #endif // hifi_AudioMixerClientData_h
diff --git a/assignment-client/src/audio/AvatarAudioRingBuffer.cpp b/assignment-client/src/audio/AvatarAudioRingBuffer.cpp
index 5613a64cc4..9c6cc32f57 100644
--- a/assignment-client/src/audio/AvatarAudioRingBuffer.cpp
+++ b/assignment-client/src/audio/AvatarAudioRingBuffer.cpp
@@ -13,12 +13,15 @@
 
 #include "AvatarAudioRingBuffer.h"
 
-AvatarAudioRingBuffer::AvatarAudioRingBuffer(bool isStereo) :
-    PositionalAudioRingBuffer(PositionalAudioRingBuffer::Microphone, isStereo) {
+AvatarAudioRingBuffer::AvatarAudioRingBuffer(bool isStereo, bool dynamicJitterBuffer) :
+    PositionalAudioRingBuffer(PositionalAudioRingBuffer::Microphone, isStereo, dynamicJitterBuffer) {
     
 }
 
 int AvatarAudioRingBuffer::parseData(const QByteArray& packet) {
+    _interframeTimeGapStats.frameReceived();
+    updateDesiredJitterBufferFrames();
+
     _shouldLoopbackForNode = (packetTypeForPacket(packet) == PacketTypeMicrophoneAudioWithEcho);
     return PositionalAudioRingBuffer::parseData(packet);
 }
diff --git a/assignment-client/src/audio/AvatarAudioRingBuffer.h b/assignment-client/src/audio/AvatarAudioRingBuffer.h
index f842c2aa33..e227e70958 100644
--- a/assignment-client/src/audio/AvatarAudioRingBuffer.h
+++ b/assignment-client/src/audio/AvatarAudioRingBuffer.h
@@ -18,7 +18,7 @@
 
 class AvatarAudioRingBuffer : public PositionalAudioRingBuffer {
 public:
-    AvatarAudioRingBuffer(bool isStereo = false);
+    AvatarAudioRingBuffer(bool isStereo = false, bool dynamicJitterBuffer = false);
     
     int parseData(const QByteArray& packet);
 private:
diff --git a/assignment-client/src/metavoxels/MetavoxelServer.cpp b/assignment-client/src/metavoxels/MetavoxelServer.cpp
index 3bf1632074..d0c0d4c781 100644
--- a/assignment-client/src/metavoxels/MetavoxelServer.cpp
+++ b/assignment-client/src/metavoxels/MetavoxelServer.cpp
@@ -69,7 +69,7 @@ void MetavoxelServer::readPendingDatagrams() {
 void MetavoxelServer::maybeAttachSession(const SharedNodePointer& node) {
     if (node->getType() == NodeType::Agent) {
         QMutexLocker locker(&node->getMutex());
-        node->setLinkedData(new MetavoxelSession(this, NodeList::getInstance()->nodeWithUUID(node->getUUID())));
+        node->setLinkedData(new MetavoxelSession(node, this));
     }
 }
 
@@ -77,7 +77,7 @@ void MetavoxelServer::sendDeltas() {
     // send deltas for all sessions
     foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
         if (node->getType() == NodeType::Agent) {
-            static_cast<MetavoxelSession*>(node->getLinkedData())->sendDelta();
+            static_cast<MetavoxelSession*>(node->getLinkedData())->update();
         }
     }
     
@@ -89,59 +89,34 @@ void MetavoxelServer::sendDeltas() {
     _sendTimer.start(qMax(0, 2 * SEND_INTERVAL - elapsed));
 }
 
-MetavoxelSession::MetavoxelSession(MetavoxelServer* server, const SharedNodePointer& node) :
-    _server(server),
-    _sequencer(byteArrayWithPopulatedHeader(PacketTypeMetavoxelData)),
-    _node(node) {
+MetavoxelSession::MetavoxelSession(const SharedNodePointer& node, MetavoxelServer* server) :
+    Endpoint(node, new PacketRecord(), NULL),
+    _server(server) {
     
-    connect(&_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendData(const QByteArray&)));
-    connect(&_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readPacket(Bitstream&)));
-    connect(&_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(clearSendRecordsBefore(int)));
     connect(&_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)), SLOT(handleMessage(const QVariant&)));
     connect(_sequencer.getReliableInputChannel(), SIGNAL(receivedMessage(const QVariant&)),
         SLOT(handleMessage(const QVariant&)));
-    
-    // insert the baseline send record
-    SendRecord record = { 0 };
-    _sendRecords.append(record);
 }
 
-MetavoxelSession::~MetavoxelSession() {
-}
-
-int MetavoxelSession::parseData(const QByteArray& packet) {
-    // process through sequencer
-    _sequencer.receivedDatagram(packet);
-    return packet.size();
-}
-
-void MetavoxelSession::sendDelta() {
+void MetavoxelSession::update() {
     // wait until we have a valid lod
-    if (!_lod.isValid()) {
-        return;
+    if (_lod.isValid()) {
+        Endpoint::update();
     }
-    Bitstream& out = _sequencer.startPacket();
+}
+
+void MetavoxelSession::writeUpdateMessage(Bitstream& out) {
     out << QVariant::fromValue(MetavoxelDeltaMessage());
-    _server->getData().writeDelta(_sendRecords.first().data, _sendRecords.first().lod, out, _lod);
-    _sequencer.endPacket();
-    
-    // record the send
-    SendRecord record = { _sequencer.getOutgoingPacketNumber(), _server->getData(), _lod };
-    _sendRecords.append(record);
+    PacketRecord* sendRecord = getLastAcknowledgedSendRecord();
+    _server->getData().writeDelta(sendRecord->getData(), sendRecord->getLOD(), out, _lod);
 }
 
-void MetavoxelSession::sendData(const QByteArray& data) {
-    NodeList::getInstance()->writeDatagram(data, _node);
-}
-
-void MetavoxelSession::readPacket(Bitstream& in) {
-    QVariant message;
-    in >> message;
+void MetavoxelSession::handleMessage(const QVariant& message, Bitstream& in) {
     handleMessage(message);
 }
 
-void MetavoxelSession::clearSendRecordsBefore(int index) {
-    _sendRecords.erase(_sendRecords.begin(), _sendRecords.begin() + index + 1);
+PacketRecord* MetavoxelSession::maybeCreateSendRecord() const {
+    return new PacketRecord(_lod, _server->getData());
 }
 
 void MetavoxelSession::handleMessage(const QVariant& message) {
diff --git a/assignment-client/src/metavoxels/MetavoxelServer.h b/assignment-client/src/metavoxels/MetavoxelServer.h
index b01bb9b412..d9b010e282 100644
--- a/assignment-client/src/metavoxels/MetavoxelServer.h
+++ b/assignment-client/src/metavoxels/MetavoxelServer.h
@@ -17,8 +17,7 @@
 
 #include <ThreadedAssignment.h>
 
-#include <DatagramSequencer.h>
-#include <MetavoxelData.h>
+#include <Endpoint.h>
 
 class MetavoxelEditMessage;
 class MetavoxelSession;
@@ -53,46 +52,31 @@ private:
 };
 
 /// Contains the state of a single client session.
-class MetavoxelSession : public NodeData {
+class MetavoxelSession : public Endpoint {
     Q_OBJECT
     
 public:
     
-    MetavoxelSession(MetavoxelServer* server, const SharedNodePointer& node);
-    virtual ~MetavoxelSession();
+    MetavoxelSession(const SharedNodePointer& node, MetavoxelServer* server);
 
-    virtual int parseData(const QByteArray& packet);
+    virtual void update();
 
-    void sendDelta();
+protected:
+
+    virtual void writeUpdateMessage(Bitstream& out);
+    virtual void handleMessage(const QVariant& message, Bitstream& in);
+    
+    virtual PacketRecord* maybeCreateSendRecord() const;
 
 private slots:
 
-    void sendData(const QByteArray& data);
-
-    void readPacket(Bitstream& in);    
-    
-    void clearSendRecordsBefore(int index);
-    
     void handleMessage(const QVariant& message);
     
 private:
     
-    class SendRecord {
-    public:
-        int packetNumber;
-        MetavoxelData data;
-        MetavoxelLOD lod;
-    };
-    
     MetavoxelServer* _server;
     
-    DatagramSequencer _sequencer;
-    
-    SharedNodePointer _node;
-    
     MetavoxelLOD _lod;
-    
-    QList<SendRecord> _sendRecords;
 };
 
 #endif // hifi_MetavoxelServer_h
diff --git a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp
index 76a6845342..3d2ca6ddc3 100644
--- a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp
+++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp
@@ -28,7 +28,8 @@ OctreeInboundPacketProcessor::OctreeInboundPacketProcessor(OctreeServer* myServe
     _totalLockWaitTime(0),
     _totalElementsInPacket(0),
     _totalPackets(0),
-    _lastNackTime(usecTimestampNow())
+    _lastNackTime(usecTimestampNow()),
+    _shuttingDown(false)
 {
 }
 
@@ -72,6 +73,10 @@ void OctreeInboundPacketProcessor::midProcess() {
 }
 
 void OctreeInboundPacketProcessor::processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet) {
+    if (_shuttingDown) {
+        qDebug() << "OctreeInboundPacketProcessor::processPacket() while shutting down... ignoring incoming packet";
+        return;
+    }
 
     bool debugProcessPacket = _myServer->wantsVerboseDebug();
 
@@ -182,8 +187,13 @@ void OctreeInboundPacketProcessor::trackInboundPacket(const QUuid& nodeUUID, uns
 }
 
 int OctreeInboundPacketProcessor::sendNackPackets() {
-
     int packetsSent = 0;
+
+    if (_shuttingDown) {
+        qDebug() << "OctreeInboundPacketProcessor::sendNackPackets() while shutting down... ignore";
+        return packetsSent;
+    }
+
     char packet[MAX_PACKET_SIZE];
     
     NodeToSenderStatsMapIterator i = _singleSenderStats.begin();
@@ -206,7 +216,7 @@ int OctreeInboundPacketProcessor::sendNackPackets() {
         }
 
         const SharedNodePointer& destinationNode = NodeList::getInstance()->getNodeHash().value(nodeUUID);
-        const QSet<unsigned short int>& missingSequenceNumbers = nodeStats.getMissingSequenceNumbers();
+        const QSet<unsigned short int>& missingSequenceNumbers = nodeStats.getIncomingEditSequenceNumberStats().getMissingSet();
         
         // construct nack packet(s) for this node
         int numSequenceNumbersAvailable = missingSequenceNumbers.size();
@@ -241,6 +251,8 @@ int OctreeInboundPacketProcessor::sendNackPackets() {
             // send it
             NodeList::getInstance()->writeUnverifiedDatagram(packet, dataAt - packet, destinationNode);
             packetsSent++;
+            
+            qDebug() << "NACK Sent back to editor/client... destinationNode=" << nodeUUID;
         }
         i++;
     }
@@ -254,8 +266,7 @@ SingleSenderStats::SingleSenderStats()
     _totalLockWaitTime(0),
     _totalElementsInPacket(0),
     _totalPackets(0),
-    _incomingLastSequence(0),
-    _missingSequenceNumbers()
+    _incomingEditSequenceNumberStats()
 {
 
 }
@@ -263,74 +274,8 @@ SingleSenderStats::SingleSenderStats()
 void SingleSenderStats::trackInboundPacket(unsigned short int incomingSequence, quint64 transitTime,
     int editsInPacket, quint64 processTime, quint64 lockWaitTime) {
 
-    const int UINT16_RANGE = std::numeric_limits<uint16_t>::max() + 1;
-    const int  MAX_REASONABLE_SEQUENCE_GAP = 1000;  // this must be less than UINT16_RANGE / 2 for rollover handling to work
-    const int MAX_MISSING_SEQUENCE_SIZE = 100;
-
-    unsigned short int expectedSequence = _totalPackets == 0 ? incomingSequence : _incomingLastSequence + (unsigned short int)1;
-    
-    if (incomingSequence == expectedSequence) { // on time
-        _incomingLastSequence = incomingSequence;
-    } else { // out of order
-        int incoming = (int)incomingSequence;
-        int expected = (int)expectedSequence;
-
-        // check if the gap between incoming and expected is reasonable, taking possible rollover into consideration
-        int absGap = std::abs(incoming - expected);
-        if (absGap >= UINT16_RANGE - MAX_REASONABLE_SEQUENCE_GAP) {
-            // rollover likely occurred between incoming and expected.
-            // correct the larger of the two so that it's within [-UINT16_RANGE, -1] while the other remains within [0, UINT16_RANGE-1]
-            if (incoming > expected) {
-                incoming -= UINT16_RANGE;
-            } else {
-                expected -= UINT16_RANGE;
-            }
-        } else if (absGap > MAX_REASONABLE_SEQUENCE_GAP) {
-            // ignore packet if gap is unreasonable
-            qDebug() << "ignoring unreasonable packet... sequence:" << incomingSequence
-                << "_incomingLastSequence:" << _incomingLastSequence;
-            return;
-        }
-        
-        // now that rollover has been corrected for (if it occurred), incoming and expected can be
-        // compared to each other directly, though one of them might be negative
-        if (incoming > expected) { // early
-            // add all sequence numbers that were skipped to the missing sequence numbers list
-            for (int missingSequence = expected; missingSequence < incoming; missingSequence++) {
-                _missingSequenceNumbers.insert(missingSequence < 0 ? missingSequence + UINT16_RANGE : missingSequence);
-            }
-            _incomingLastSequence = incomingSequence;
-        } else { // late
-            // remove this from missing sequence number if it's in there
-            _missingSequenceNumbers.remove(incomingSequence);
-
-            // do not update _incomingLastSequence; it shouldn't become smaller
-        }
-    }
-
-    // prune missing sequence list if it gets too big; sequence numbers that are older than MAX_REASONABLE_SEQUENCE_GAP
-    // will be removed.
-    if (_missingSequenceNumbers.size() > MAX_MISSING_SEQUENCE_SIZE) {
-        // some older sequence numbers may be from before a rollover point; this must be handled.
-        // some sequence numbers in this list may be larger than _incomingLastSequence, indicating that they were received
-        // before the most recent rollover.
-        int cutoff = (int)_incomingLastSequence - MAX_REASONABLE_SEQUENCE_GAP;
-        if (cutoff >= 0) {
-            foreach(unsigned short int missingSequence, _missingSequenceNumbers) {
-                unsigned short int nonRolloverCutoff = (unsigned short int)cutoff;
-                if (missingSequence > _incomingLastSequence || missingSequence <= nonRolloverCutoff) {
-                    _missingSequenceNumbers.remove(missingSequence);
-                }
-            }
-        } else {
-            unsigned short int rolloverCutoff = (unsigned short int)(cutoff + UINT16_RANGE);
-            foreach(unsigned short int missingSequence, _missingSequenceNumbers) {
-                if (missingSequence > _incomingLastSequence && missingSequence <= rolloverCutoff) {
-                    _missingSequenceNumbers.remove(missingSequence);
-                }
-            }
-        }
-    }
+    // track sequence number
+    _incomingEditSequenceNumberStats.sequenceNumberReceived(incomingSequence);
 
     // update other stats
     _totalTransitTime += transitTime;
diff --git a/assignment-client/src/octree/OctreeInboundPacketProcessor.h b/assignment-client/src/octree/OctreeInboundPacketProcessor.h
index 46a57205cb..960282384b 100644
--- a/assignment-client/src/octree/OctreeInboundPacketProcessor.h
+++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.h
@@ -14,9 +14,10 @@
 #ifndef hifi_OctreeInboundPacketProcessor_h
 #define hifi_OctreeInboundPacketProcessor_h
 
-#include <map>
-
 #include <ReceivedPacketProcessor.h>
+
+#include "SequenceNumberStats.h"
+
 class OctreeServer;
 
 class SingleSenderStats {
@@ -32,7 +33,8 @@ public:
                 { return _totalElementsInPacket == 0 ? 0 : _totalProcessTime / _totalElementsInPacket; }
     quint64 getAverageLockWaitTimePerElement() const 
                 { return _totalElementsInPacket == 0 ? 0 : _totalLockWaitTime / _totalElementsInPacket; }
-    const QSet<unsigned short int>& getMissingSequenceNumbers() const { return _missingSequenceNumbers; }
+    
+    const SequenceNumberStats& getIncomingEditSequenceNumberStats() const { return _incomingEditSequenceNumberStats; }
 
     void trackInboundPacket(unsigned short int incomingSequence, quint64 transitTime,
         int editsInPacket, quint64 processTime, quint64 lockWaitTime);
@@ -42,9 +44,7 @@ public:
     quint64 _totalLockWaitTime;
     quint64 _totalElementsInPacket;
     quint64 _totalPackets;
-
-    unsigned short int _incomingLastSequence;
-    QSet<unsigned short int> _missingSequenceNumbers;
+    SequenceNumberStats _incomingEditSequenceNumberStats;
 };
 
 typedef QHash<QUuid, SingleSenderStats> NodeToSenderStatsMap;
@@ -73,6 +73,8 @@ public:
 
     NodeToSenderStatsMap& getSingleSenderStats() { return _singleSenderStats; }
 
+    void shuttingDown() { _shuttingDown = true;}
+
 protected:
 
     virtual void processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet);
@@ -100,5 +102,6 @@ private:
     NodeToSenderStatsMap _singleSenderStats;
 
     quint64 _lastNackTime;
+    bool _shuttingDown;
 };
 #endif // hifi_OctreeInboundPacketProcessor_h
diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp
index 48c8674c03..23719b86cf 100644
--- a/assignment-client/src/octree/OctreeServer.cpp
+++ b/assignment-client/src/octree/OctreeServer.cpp
@@ -1097,6 +1097,8 @@ void OctreeServer::forceNodeShutdown(SharedNodePointer node) {
 
 void OctreeServer::aboutToFinish() {
     qDebug() << qPrintable(_safeServerName) << "server STARTING about to finish...";
+    qDebug() << qPrintable(_safeServerName) << "inform Octree Inbound Packet Processor that we are shutting down...";
+    _octreeInboundPacketProcessor->shuttingDown();
     foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
         qDebug() << qPrintable(_safeServerName) << "server about to finish while node still connected node:" << *node;
         forceNodeShutdown(node);
diff --git a/cmake/modules/FindLibOVR.cmake b/cmake/modules/FindLibOVR.cmake
index f65088e817..91714e8a59 100644
--- a/cmake/modules/FindLibOVR.cmake
+++ b/cmake/modules/FindLibOVR.cmake
@@ -42,14 +42,11 @@ else (LIBOVR_LIBRARIES AND LIBOVR_INCLUDE_DIRS)
     if (UDEV_LIBRARY AND XINERAMA_LIBRARY AND OVR_LIBRARY)
       set(LIBOVR_LIBRARIES "${OVR_LIBRARY};${UDEV_LIBRARY};${XINERAMA_LIBRARY}" CACHE INTERNAL "Oculus libraries")
     endif (UDEV_LIBRARY AND XINERAMA_LIBRARY AND OVR_LIBRARY)
-  elseif (WIN32)    
-    if (CMAKE_BUILD_TYPE MATCHES DEBUG)
-      set(WINDOWS_LIBOVR_NAME "libovrd.lib")
-    else()
-      set(WINDOWS_LIBOVR_NAME "libovr.lib")
-    endif()
+  elseif (WIN32)      
+    find_library(LIBOVR_RELEASE_LIBRARIES "Lib/Win32/libovr.lib" HINTS ${LIBOVR_SEARCH_DIRS})
+    find_library(LIBOVR_DEBUG_LIBRARIES "Lib/Win32/libovrd.lib" HINTS ${LIBOVR_SEARCH_DIRS})
     
-    find_library(LIBOVR_LIBRARIES "Lib/Win32/${WINDOWS_LIBOVR_NAME}" HINTS ${LIBOVR_SEARCH_DIRS})
+    set(LIBOVR_LIBRARIES "${LIBOVR_RELEASE_LIBRARIES} ${LIBOVR_DEBUG_LIBRARIES}")
   endif ()
 
   if (LIBOVR_INCLUDE_DIRS AND LIBOVR_LIBRARIES)
diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css
index ff33cc206b..3b60ada78b 100644
--- a/domain-server/resources/web/css/style.css
+++ b/domain-server/resources/web/css/style.css
@@ -1,8 +1,8 @@
-#nodes-lead {
+#nodes-lead, #settings-lead {
   color: #66CCCC;
 }
 
-#nodes-lead .lead-line {
+#nodes-lead .lead-line, #settings-lead .lead-line {
   background-color: #66CCCC;
 }
 
diff --git a/domain-server/resources/web/index.shtml b/domain-server/resources/web/index.shtml
index b6ba8f67db..f0315a113f 100644
--- a/domain-server/resources/web/index.shtml
+++ b/domain-server/resources/web/index.shtml
@@ -18,7 +18,20 @@
       </tr>
     </thead>
     <tbody>
-    </tbody>
+      <script id="nodes-template" type="text/template">
+        <% _.each(nodes, function(node, node_index){ %>
+          <tr>
+            <td><%- node.type %></td>
+            <td><a href="stats/?uuid=<%- node.uuid %>"><%- node.uuid %></a></td>
+            <td><%- node.pool %></td>
+            <td><%- node.public.ip %><span class='port'><%- node.public.port %></span></td>
+            <td><%- node.local.ip %><span class='port'><%- node.local.port %></span></td>
+            <td><%- ((Date.now() - node.wake_timestamp) /  1000).toLocaleString() %></td>
+            <td><%- (typeof node.pending_credits == 'number' ? node.pending_credits.toLocaleString() : 'N/A') %></td>
+            <td><span class='glyphicon glyphicon-remove' data-uuid="<%- node.uuid %>"></span></td>
+          </tr>
+        <% }); %>
+      </script>
   </table>
   
   <div id="queued-lead" class="table-lead"><h3>Queued Assignments</h3><div class="lead-line"></div></div>
@@ -31,8 +44,18 @@
       </tr>
     </thead>
     <tbody>
+      <script id="queued-template" type="text/template">
+        <% _.each(queued, function(assignment, uuid){ %>
+        <tr>
+          <td><%- assignment.type %></td>
+          <td><%- uuid %></td>
+          <td><%- assignment.pool %></td>
+        </tr>
+        <% }); %>
+      </script>
     </tbody>
   </table>
 <!--#include file="footer.html"-->
 <script src='js/tables.js'></script>
+<script src='js/underscore-1.5.0.min.js'></script>
 <!--#include file="page-end.html"-->
\ No newline at end of file
diff --git a/domain-server/resources/web/js/form2js.min.js b/domain-server/resources/web/js/form2js.min.js
new file mode 100755
index 0000000000..f1e610f7c3
--- /dev/null
+++ b/domain-server/resources/web/js/form2js.min.js
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2010 Maxim Vasiliev
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author Maxim Vasiliev
+ * Date: 09.09.2010
+ * Time: 19:02:33
+ */
+(function(e,t){if(typeof define==="function"&&define.amd){define(t)}else{e.form2js=t()}})(this,function(){"use strict";function e(e,r,i,s,o,u){u=u?true:false;if(typeof i=="undefined"||i==null)i=true;if(typeof r=="undefined"||r==null)r=".";if(arguments.length<5)o=false;e=typeof e=="string"?document.getElementById(e):e;var a=[],f,l=0;if(e.constructor==Array||typeof NodeList!="undefined"&&e.constructor==NodeList){while(f=e[l++]){a=a.concat(n(f,s,o,u))}}else{a=n(e,s,o,u)}return t(a,i,r)}function t(e,t,n){var r={},i={},s,o,u,a,f,l,c,h,p,d,v,m,g;for(s=0;s<e.length;s++){f=e[s].value;if(t&&(f===""||f===null))continue;m=e[s].name;g=m.split(n);l=[];c=r;h="";for(o=0;o<g.length;o++){v=g[o].split("][");if(v.length>1){for(u=0;u<v.length;u++){if(u==0){v[u]=v[u]+"]"}else if(u==v.length-1){v[u]="["+v[u]}else{v[u]="["+v[u]+"]"}d=v[u].match(/([a-z_]+)?\[([a-z_][a-z0-9_]+?)\]/i);if(d){for(a=1;a<d.length;a++){if(d[a])l.push(d[a])}}else{l.push(v[u])}}}else l=l.concat(v)}for(o=0;o<l.length;o++){v=l[o];if(v.indexOf("[]")>-1&&o==l.length-1){p=v.substr(0,v.indexOf("["));h+=p;if(!c[p])c[p]=[];c[p].push(f)}else if(v.indexOf("[")>-1){p=v.substr(0,v.indexOf("["));d=v.replace(/(^([a-z_]+)?\[)|(\]$)/gi,"");h+="_"+p+"_"+d;if(!i[h])i[h]={};if(p!=""&&!c[p])c[p]=[];if(o==l.length-1){if(p==""){c.push(f);i[h][d]=c[c.length-1]}else{c[p].push(f);i[h][d]=c[p][c[p].length-1]}}else{if(!i[h][d]){if(/^[0-9a-z_]+\[?/i.test(l[o+1]))c[p].push({});else c[p].push([]);i[h][d]=c[p][c[p].length-1]}}c=i[h][d]}else{h+=v;if(o<l.length-1){if(!c[v])c[v]={};c=c[v]}else{c[v]=f}}}}return r}function n(e,t,n,s){var o=i(e,t,n,s);return o.length>0?o:r(e,t,n,s)}function r(e,t,n,r){var s=[],o=e.firstChild;while(o){s=s.concat(i(o,t,n,r));o=o.nextSibling}return s}function i(e,t,n,i){if(e.disabled&&!i)return[];var u,a,f,l=s(e,n);u=t&&t(e);if(u&&u.name){f=[u]}else if(l!=""&&e.nodeName.match(/INPUT|TEXTAREA/i)){a=o(e,i);if(null===a){f=[]}else{f=[{name:l,value:a}]}}else if(l!=""&&e.nodeName.match(/SELECT/i)){a=o(e,i);f=[{name:l.replace(/\[\]$/,""),value:a}]}else{f=r(e,t,n,i)}return f}function s(e,t){if(e.name&&e.name!="")return e.name;else if(t&&e.id&&e.id!="")return e.id;else return""}function o(e,t){if(e.disabled&&!t)return null;switch(e.nodeName){case"INPUT":case"TEXTAREA":switch(e.type.toLowerCase()){case"radio":if(e.checked&&e.value==="false")return false;case"checkbox":if(e.checked&&e.value==="true")return true;if(!e.checked&&e.value==="true")return false;if(e.checked)return e.value;break;case"button":case"reset":case"submit":case"image":return"";break;default:return e.value;break}break;case"SELECT":return u(e);break;default:break}return null}function u(e){var t=e.multiple,n=[],r,i,s;if(!t)return e.value;for(r=e.getElementsByTagName("option"),i=0,s=r.length;i<s;i++){if(r[i].selected)n.push(r[i].value)}return n}return e})
\ No newline at end of file
diff --git a/domain-server/resources/web/js/settings.js b/domain-server/resources/web/js/settings.js
new file mode 100644
index 0000000000..487ec5b296
--- /dev/null
+++ b/domain-server/resources/web/js/settings.js
@@ -0,0 +1,72 @@
+var Settings = {};
+
+$(document).ready(function(){  
+  var source = $('#settings-template').html();
+  Settings.template = _.template(source);
+  
+  reloadSettings();
+});
+
+function reloadSettings() {
+  $.getJSON('/settings.json', function(data){
+    $('#settings').html(Settings.template(data));
+  });
+}
+
+var SETTINGS_ERROR_MESSAGE = "There was a problem saving domain settings. Please try again!";
+
+$('#settings').on('click', 'button', function(e){
+  // disable any inputs not changed
+  $("input:not([data-changed])").each(function(){
+    $(this).prop('disabled', true);
+  });
+  
+  // grab a JSON representation of the form via form2js
+  var formJSON = form2js('settings-form', ".", false, cleanupFormValues, true);
+  
+  // re-enable all inputs
+  $("input").each(function(){
+    $(this).prop('disabled', false);
+  });
+  
+  // POST the form JSON to the domain-server settings.json endpoint so the settings are saved
+  $.ajax('/settings.json', {
+    data: JSON.stringify(formJSON),
+    contentType: 'application/json',
+    type: 'POST'
+  }).done(function(data){
+    if (data.status == "success") {
+      showAlertMessage("Domain settings saved.", true);
+    } else {
+      showAlertMessage(SETTINGS_ERROR_MESSAGE, false);
+    }
+    
+    reloadSettings();
+  }).fail(function(){
+    showAlertMessage(SETTINGS_ERROR_MESSAGE, false);
+    reloadSettings();
+  });
+  
+  return false;
+});
+
+$('#settings').on('change', 'input', function(){
+  // this input was changed, add the changed data attribute to it
+  $(this).attr('data-changed', true);
+});
+
+function cleanupFormValues(node) {  
+  if (node.type && node.type === 'checkbox') {
+    return { name: node.id, value: node.checked ? true : false };
+  } else {
+    return false;
+  }
+}
+
+function showAlertMessage(message, isSuccess) {
+  var alertBox = $('.alert');
+  alertBox.attr('class', 'alert');
+  alertBox.addClass(isSuccess ? 'alert-success' : 'alert-danger');
+  alertBox.html(message);
+  alertBox.fadeIn();
+}
\ No newline at end of file
diff --git a/domain-server/resources/web/js/tables.js b/domain-server/resources/web/js/tables.js
index b564d9392f..0b29d4e6c9 100644
--- a/domain-server/resources/web/js/tables.js
+++ b/domain-server/resources/web/js/tables.js
@@ -1,4 +1,8 @@
 $(document).ready(function(){
+  // setup the underscore templates
+  var nodeTemplate = _.template($('#nodes-template').html());
+  var queuedTemplate = _.template($('#queued-template').html());
+  
   // setup a function to grab the assignments
   function getNodesAndAssignments() {
     $.getJSON("nodes.json", function(json){
@@ -29,40 +33,11 @@ $(document).ready(function(){
           }   
       });
       
-      nodesTableBody = "";
-      
-      $.each(json.nodes, function(index, data) {
-        nodesTableBody += "<tr>";
-        nodesTableBody += "<td>" + data.type + "</td>";
-        nodesTableBody += "<td><a href='stats/?uuid=" + data.uuid + "'>" + data.uuid + "</a></td>";
-        nodesTableBody += "<td>" + (data.pool ? data.pool : "") + "</td>";
-        nodesTableBody += "<td>" + data.public.ip + "<span class='port'>:" + data.public.port + "</span></td>";
-        nodesTableBody += "<td>" + data.local.ip + "<span class='port'>:" + data.local.port + "</span></td>";
-        
-        var uptimeSeconds = (Date.now() - data.wake_timestamp) / 1000;
-        nodesTableBody += "<td>" + uptimeSeconds.toLocaleString() + "</td>";
-        
-        nodesTableBody += "<td>" + (typeof data.pending_credits == 'number' ? data.pending_credits.toLocaleString() : 'N/A') + "</td>";
-        
-        nodesTableBody += "<td><span class='glyphicon glyphicon-remove' data-uuid=" + data.uuid + "></span></td>";
-        nodesTableBody += "</tr>";
-      });
-      
-      $('#nodes-table tbody').html(nodesTableBody);
+      $('#nodes-table tbody').html(nodeTemplate(json));
     });
     
     $.getJSON("assignments.json", function(json){      
-      queuedTableBody = "";
-        
-      $.each(json.queued, function (uuid, data) {
-        queuedTableBody += "<tr>";
-        queuedTableBody += "<td>" + data.type + "</td>";
-        queuedTableBody += "<td>" + uuid + "</td>";
-        queuedTableBody += "<td>" + (data.pool ? data.pool : "") + "</td>";
-        queuedTableBody += "</tr>";
-      });
-      
-      $('#assignments-table tbody').html(queuedTableBody);
+      $('#assignments-table tbody').html(queuedTemplate(json));
     });
   }
   
diff --git a/domain-server/resources/web/js/underscore-1.5.0.min.js b/domain-server/resources/web/js/underscore-1.5.0.min.js
new file mode 100644
index 0000000000..4db9729997
--- /dev/null
+++ b/domain-server/resources/web/js/underscore-1.5.0.min.js
@@ -0,0 +1,7 @@
+//     Underscore.js 1.5.0
+//     http://underscorejs.org
+//     (c) 2009-2011 Jeremy Ashkenas, DocumentCloud Inc.
+//     (c) 2011-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+//     Underscore may be freely distributed under the MIT license.
+!function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,v=e.reduce,h=e.reduceRight,d=e.filter,g=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,_=Object.keys,w=i.bind,j=function(n){return n instanceof j?n:this instanceof j?(this._wrapped=n,void 0):new j(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=j),exports._=j):n._=j,j.VERSION="1.5.0";var A=j.each=j.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a in n)if(j.has(n,a)&&t.call(e,n[a],a,n)===r)return};j.map=j.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e.push(t.call(r,n,u,i))}),e)};var E="Reduce of empty array with no initial value";j.reduce=j.foldl=j.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduce===v)return e&&(t=j.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(E);return r},j.reduceRight=j.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduceRight===h)return e&&(t=j.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=j.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(E);return r},j.find=j.detect=function(n,t,r){var e;return O(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},j.filter=j.select=function(n,t,r){var e=[];return null==n?e:d&&n.filter===d?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&e.push(n)}),e)},j.reject=function(n,t,r){return j.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},j.every=j.all=function(n,t,e){t||(t=j.identity);var u=!0;return null==n?u:g&&n.every===g?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var O=j.some=j.any=function(n,t,e){t||(t=j.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};j.contains=j.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:O(n,function(n){return n===t})},j.invoke=function(n,t){var r=o.call(arguments,2),e=j.isFunction(t);return j.map(n,function(n){return(e?t:n[t]).apply(n,r)})},j.pluck=function(n,t){return j.map(n,function(n){return n[t]})},j.where=function(n,t,r){return j.isEmpty(t)?r?void 0:[]:j[r?"find":"filter"](n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},j.findWhere=function(n,t){return j.where(n,t,!0)},j.max=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.max.apply(Math,n);if(!t&&j.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>e.computed&&(e={value:n,computed:a})}),e.value},j.min=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.min.apply(Math,n);if(!t&&j.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a<e.computed&&(e={value:n,computed:a})}),e.value},j.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=j.random(r++),e[r-1]=e[t],e[t]=n}),e};var F=function(n){return j.isFunction(n)?n:function(t){return t[n]}};j.sortBy=function(n,t,r){var e=F(t);return j.pluck(j.map(n,function(n,t,u){return{value:n,index:t,criteria:e.call(r,n,t,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index<t.index?-1:1}),"value")};var k=function(n,t,r,e){var u={},i=F(null==t?j.identity:t);return A(n,function(t,a){var o=i.call(r,t,a,n);e(u,o,t)}),u};j.groupBy=function(n,t,r){return k(n,t,r,function(n,t,r){(j.has(n,t)?n[t]:n[t]=[]).push(r)})},j.countBy=function(n,t,r){return k(n,t,r,function(n,t){j.has(n,t)||(n[t]=0),n[t]++})},j.sortedIndex=function(n,t,r,e){r=null==r?j.identity:F(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;r.call(e,n[o])<u?i=o+1:a=o}return i},j.toArray=function(n){return n?j.isArray(n)?o.call(n):n.length===+n.length?j.map(n,j.identity):j.values(n):[]},j.size=function(n){return null==n?0:n.length===+n.length?n.length:j.keys(n).length},j.first=j.head=j.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:o.call(n,0,t)},j.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},j.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},j.rest=j.tail=j.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},j.compact=function(n){return j.filter(n,j.identity)};var R=function(n,t,r){return t&&j.every(n,j.isArray)?c.apply(r,n):(A(n,function(n){j.isArray(n)||j.isArguments(n)?t?a.apply(r,n):R(n,t,r):r.push(n)}),r)};j.flatten=function(n,t){return R(n,t,[])},j.without=function(n){return j.difference(n,o.call(arguments,1))},j.uniq=j.unique=function(n,t,r,e){j.isFunction(t)&&(e=r,r=t,t=!1);var u=r?j.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:j.contains(a,r))||(a.push(r),i.push(n[e]))}),i},j.union=function(){return j.uniq(j.flatten(arguments,!0))},j.intersection=function(n){var t=o.call(arguments,1);return j.filter(j.uniq(n),function(n){return j.every(t,function(t){return j.indexOf(t,n)>=0})})},j.difference=function(n){var t=c.apply(e,o.call(arguments,1));return j.filter(n,function(n){return!j.contains(t,n)})},j.zip=function(){return j.unzip.apply(j,o.call(arguments))},j.unzip=function(){for(var n=j.max(j.pluck(arguments,"length").concat(0)),t=new Array(n),r=0;n>r;r++)t[r]=j.pluck(arguments,""+r);return t},j.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},j.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=j.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},j.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},j.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=new Array(e);e>u;)i[u++]=n,n+=r;return i};var M=function(){};j.bind=function(n,t){var r,e;if(w&&n.bind===w)return w.apply(n,o.call(arguments,1));if(!j.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));M.prototype=n.prototype;var u=new M;M.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},j.partial=function(n){var t=o.call(arguments,1);return function(){return n.apply(this,t.concat(o.call(arguments)))}},j.bindAll=function(n){var t=o.call(arguments,1);if(0===t.length)throw new Error("bindAll must be passed function names");return A(t,function(t){n[t]=j.bind(n[t],n)}),n},j.memoize=function(n,t){var r={};return t||(t=j.identity),function(){var e=t.apply(this,arguments);return j.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},j.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},j.defer=function(n){return j.delay.apply(j,[n,1].concat(o.call(arguments,1)))},j.throttle=function(n,t,r){var e,u,i,a=null,o=0;r||(r={});var c=function(){o=new Date,a=null,i=n.apply(e,u)};return function(){var l=new Date;o||r.leading!==!1||(o=l);var f=t-(l-o);return e=this,u=arguments,0>=f?(clearTimeout(a),a=null,o=l,i=n.apply(e,u)):a||r.trailing===!1||(a=setTimeout(c,f)),i}},j.debounce=function(n,t,r){var e,u=null;return function(){var i=this,a=arguments,o=function(){u=null,r||(e=n.apply(i,a))},c=r&&!u;return clearTimeout(u),u=setTimeout(o,t),c&&(e=n.apply(i,a)),e}},j.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},j.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},j.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},j.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},j.keys=_||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)j.has(n,r)&&t.push(r);return t},j.values=function(n){var t=[];for(var r in n)j.has(n,r)&&t.push(n[r]);return t},j.pairs=function(n){var t=[];for(var r in n)j.has(n,r)&&t.push([r,n[r]]);return t},j.invert=function(n){var t={};for(var r in n)j.has(n,r)&&(t[n[r]]=r);return t},j.functions=j.methods=function(n){var t=[];for(var r in n)j.isFunction(n[r])&&t.push(r);return t.sort()},j.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},j.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},j.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)j.contains(r,u)||(t[u]=n[u]);return t},j.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]===void 0&&(n[r]=t[r])}),n},j.clone=function(n){return j.isObject(n)?j.isArray(n)?n.slice():j.extend({},n):n},j.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof j&&(n=n._wrapped),t instanceof j&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==String(t);case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;var a=n.constructor,o=t.constructor;if(a!==o&&!(j.isFunction(a)&&a instanceof a&&j.isFunction(o)&&o instanceof o))return!1;r.push(n),e.push(t);var c=0,f=!0;if("[object Array]"==u){if(c=n.length,f=c==t.length)for(;c--&&(f=S(n[c],t[c],r,e)););}else{for(var s in n)if(j.has(n,s)&&(c++,!(f=j.has(t,s)&&S(n[s],t[s],r,e))))break;if(f){for(s in t)if(j.has(t,s)&&!c--)break;f=!c}}return r.pop(),e.pop(),f};j.isEqual=function(n,t){return S(n,t,[],[])},j.isEmpty=function(n){if(null==n)return!0;if(j.isArray(n)||j.isString(n))return 0===n.length;for(var t in n)if(j.has(n,t))return!1;return!0},j.isElement=function(n){return!(!n||1!==n.nodeType)},j.isArray=x||function(n){return"[object Array]"==l.call(n)},j.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){j["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),j.isArguments(arguments)||(j.isArguments=function(n){return!(!n||!j.has(n,"callee"))}),"function"!=typeof/./&&(j.isFunction=function(n){return"function"==typeof n}),j.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},j.isNaN=function(n){return j.isNumber(n)&&n!=+n},j.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},j.isNull=function(n){return null===n},j.isUndefined=function(n){return n===void 0},j.has=function(n,t){return f.call(n,t)},j.noConflict=function(){return n._=t,this},j.identity=function(n){return n},j.times=function(n,t,r){for(var e=Array(Math.max(0,n)),u=0;n>u;u++)e[u]=t.call(r,u);return e},j.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))};var I={escape:{"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","/":"&#x2F;"}};I.unescape=j.invert(I.escape);var T={escape:new RegExp("["+j.keys(I.escape).join("")+"]","g"),unescape:new RegExp("("+j.keys(I.unescape).join("|")+")","g")};j.each(["escape","unescape"],function(n){j[n]=function(t){return null==t?"":(""+t).replace(T[n],function(t){return I[n][t]})}}),j.result=function(n,t){if(null==n)return void 0;var r=n[t];return j.isFunction(r)?r.call(n):r},j.mixin=function(n){A(j.functions(n),function(t){var r=j[t]=n[t];j.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),D.call(this,r.apply(j,n))}})};var N=0;j.uniqueId=function(n){var t=++N+"";return n?n+t:t},j.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n","	":"t","\u2028":"u2028","\u2029":"u2029"},z=/\\|'|\r|\n|\t|\u2028|\u2029/g;j.template=function(n,t,r){var e;r=j.defaults({},r,j.templateSettings);var u=new RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(z,function(n){return"\\"+B[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=new Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,j);var c=function(n){return e.call(this,n,j)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},j.chain=function(n){return j(n).chain()};var D=function(n){return this._chain?j(n).chain():n};j.mixin(j),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];j.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],D.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];j.prototype[n]=function(){return D.call(this,t.apply(this._wrapped,arguments))}}),j.extend(j.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}.call(this);
+//# sourceMappingURL=underscore-min.map
\ No newline at end of file
diff --git a/domain-server/resources/web/settings/describe.json b/domain-server/resources/web/settings/describe.json
new file mode 100644
index 0000000000..227b6bf0cd
--- /dev/null
+++ b/domain-server/resources/web/settings/describe.json
@@ -0,0 +1,20 @@
+{
+  "audio": {
+    "label": "Audio",
+    "assignment-types": [0],
+    "settings": {
+      "unattenuated-zone": {
+        "label": "Unattenuated Zone",
+        "help": "Boxes for source and listener (corner x, corner y, corner z, size x, size y, size z, corner x, corner y, corner z, size x, size y, size z)",
+        "placeholder": "no zone",
+        "default": ""
+      },
+      "dynamic-jitter-buffer": {
+        "type": "checkbox",
+        "label": "Dynamic Jitter Buffers",
+        "help": "Dynamically buffer client audio based on perceived jitter in packet receipt timing",
+        "default": false
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/domain-server/resources/web/settings/index.shtml b/domain-server/resources/web/settings/index.shtml
new file mode 100644
index 0000000000..3bb669b32e
--- /dev/null
+++ b/domain-server/resources/web/settings/index.shtml
@@ -0,0 +1,46 @@
+<!--#include virtual="header.html"-->
+<div id="settings-lead" class="table-lead"><h3>Settings</h3><div class="lead-line"></div></div>
+<div style="clear: both;"></div>
+<div class="alert" style="display:none;"></div>
+<form class="form-horizontal" id="settings-form" role="form">
+  
+<script id="settings-template" type="text/template">
+  <% _.each(descriptions, function(group, group_key){ %>
+    <div class="panel panel-default">
+      <div class="panel-heading">
+        <h3 class="panel-title"><%- group.label %></h3>
+      </div>
+      <div class="panel-body">
+        <% _.each(group.settings, function(setting, setting_key){ %>
+          <div class="form-group">
+            <% var setting_id = group_key + "." + setting_key %>
+            <label for="<%- setting_id %>" class="col-sm-2 control-label"><%- setting.label %></label>
+            <div class="col-sm-10">
+              <% if(setting.type) %>
+                <% if (setting.type === "checkbox") { %>
+                  <% var checked_box = (values[group_key] || {})[setting_key] || setting.default %>
+                  <input type="checkbox" id="<%- setting_id %>" <%- checked_box ? "checked" : "" %>>
+                <% } else { %>
+                <input type="text" class="form-control" id="<%- setting_id %>" 
+                  placeholder="<%- setting.placeholder %>" 
+                  value="<%- (values[group_key] || {})[setting_key] %>">
+              <% } %>
+              
+            </div>
+            <p class="help-block col-sm-offset-2 col-sm-10"><%- setting.help %></p>
+          </div>
+        <% }); %>
+      </div>
+    </div>
+  <% }); %>
+  <button type="submit" class="btn btn-default">Save</button>
+</script>
+
+<div id="settings"></div>
+ </form>
+ 
+<!--#include virtual="footer.html"-->
+<script src='/js/settings.js'></script>
+<script src='/js/form2js.min.js'></script>
+<script src='/js/underscore-1.5.0.min.js'></script>
+<!--#include virtual="page-end.html"-->
\ No newline at end of file
diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp
index d55a9b52ca..7a2d5f4f99 100644
--- a/domain-server/src/DomainServer.cpp
+++ b/domain-server/src/DomainServer.cpp
@@ -41,7 +41,8 @@ DomainServer::DomainServer(int argc, char* argv[]) :
     _oauthClientID(),
     _hostname(),
     _networkReplyUUIDMap(),
-    _sessionAuthenticationHash()
+    _sessionAuthenticationHash(),
+    _settingsManager()
 {
     setOrganizationName("High Fidelity");
     setOrganizationDomain("highfidelity.io");
@@ -362,7 +363,7 @@ void DomainServer::createStaticAssignmentsForType(Assignment::Type type, const Q
                 QString dashes = payloadKey.size() == 1 ? "-" : "--";
                 payloadStringList << QString("%1%2 %3").arg(dashes).arg(payloadKey).arg(jsonObject[payloadKey].toString());
             }
-
+            
             configAssignment->setPayload(payloadStringList.join(' ').toUtf8());
 
             addStaticAssignmentToAssignmentHash(configAssignment);
@@ -1162,12 +1163,13 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
         }
     }
 
-    // didn't process the request, let the HTTPManager try and handle
-    return false;
+    // didn't process the request, let our DomainServerSettingsManager or HTTPManager handle
+    return _settingsManager.handleHTTPRequest(connection, url);
 }
 
 bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &url) {
     const QString URI_OAUTH = "/oauth";
+    qDebug() << "HTTPS request received at" << url.toString();
     if (url.path() == URI_OAUTH) {
 
         QUrlQuery codeURLQuery(url);
diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h
index b038850b3d..01f44b698e 100644
--- a/domain-server/src/DomainServer.h
+++ b/domain-server/src/DomainServer.h
@@ -24,6 +24,7 @@
 #include <HTTPSConnection.h>
 #include <LimitedNodeList.h>
 
+#include "DomainServerSettingsManager.h"
 #include "WalletTransaction.h"
 
 #include "PendingAssignedNodeData.h"
@@ -110,6 +111,8 @@ private:
     QString _hostname;
     QMap<QNetworkReply*, QUuid> _networkReplyUUIDMap;
     QHash<QUuid, bool> _sessionAuthenticationHash;
+    
+    DomainServerSettingsManager _settingsManager;
 };
 
 #endif // hifi_DomainServer_h
diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp
new file mode 100644
index 0000000000..d7e2e05ca8
--- /dev/null
+++ b/domain-server/src/DomainServerSettingsManager.cpp
@@ -0,0 +1,179 @@
+//
+//  DomainServerSettingsManager.cpp
+//  domain-server/src
+//
+//  Created by Stephen Birarda on 2014-06-24.
+//  Copyright 2014 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
+//
+
+#include <QtCore/QCoreApplication>
+#include <QtCore/QFile>
+#include <QtCore/QJsonArray>
+#include <QtCore/QJsonObject>
+#include <QtCore/QUrl>
+#include <QtCore/QUrlQuery>
+
+#include <Assignment.h>
+#include <HTTPConnection.h>
+
+#include "DomainServerSettingsManager.h"
+
+const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/web/settings/describe.json";
+const QString SETTINGS_CONFIG_FILE_RELATIVE_PATH = "/resources/config.json";
+
+DomainServerSettingsManager::DomainServerSettingsManager() :
+    _descriptionObject(),
+    _settingsMap()
+{
+    // load the description object from the settings description
+    QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH);
+    descriptionFile.open(QIODevice::ReadOnly);
+    
+    _descriptionObject = QJsonDocument::fromJson(descriptionFile.readAll()).object();
+    
+    // load the existing config file to get the current values
+    QFile configFile(QCoreApplication::applicationDirPath() + SETTINGS_CONFIG_FILE_RELATIVE_PATH);
+    configFile.open(QIODevice::ReadOnly);
+    
+    _settingsMap = QJsonDocument::fromJson(configFile.readAll()).toVariant().toMap();
+}
+
+const QString DESCRIPTION_SETTINGS_KEY = "settings";
+const QString SETTING_DEFAULT_KEY = "default";
+
+bool DomainServerSettingsManager::handleHTTPRequest(HTTPConnection* connection, const QUrl &url) {
+    if (connection->requestOperation() == QNetworkAccessManager::PostOperation && url.path() == "/settings.json") {
+        // this is a POST operation to change one or more settings
+        QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent());
+        QJsonObject postedObject = postedDocument.object();
+        
+        // we recurse one level deep below each group for the appropriate setting
+        recurseJSONObjectAndOverwriteSettings(postedObject, _settingsMap, _descriptionObject);
+        
+        // store whatever the current _settingsMap is to file
+        persistToFile();
+        
+        // return success to the caller
+        QString jsonSuccess = "{\"status\": \"success\"}";
+        connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
+        
+        return true;
+    } else if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == "/settings.json") {
+        // this is a GET operation for our settings
+        
+        // check if there is a query parameter for settings affecting a particular type of assignment
+        const QString SETTINGS_TYPE_QUERY_KEY = "type";
+        QUrlQuery settingsQuery(url);
+        QString typeValue = settingsQuery.queryItemValue(SETTINGS_TYPE_QUERY_KEY);
+        
+        QJsonObject responseObject;
+        
+        if (typeValue.isEmpty()) {
+            // combine the description object and our current settings map
+            responseObject["descriptions"] = _descriptionObject;
+            responseObject["values"] = QJsonDocument::fromVariant(_settingsMap).object();
+        } else {
+            // convert the string type value to a QJsonValue
+            QJsonValue queryType = QJsonValue(typeValue.toInt());
+            
+            const QString AFFECTED_TYPES_JSON_KEY = "assignment-types";
+            
+            // enumerate the groups in the description object to find which settings to pass
+            foreach(const QString& group, _descriptionObject.keys()) {
+                QJsonObject groupObject = _descriptionObject[group].toObject();
+                QJsonObject groupSettingsObject = groupObject[DESCRIPTION_SETTINGS_KEY].toObject();
+                
+                QJsonObject groupResponseObject;
+                
+                
+                foreach(const QString& settingKey, groupSettingsObject.keys()) {
+                    QJsonObject settingObject = groupSettingsObject[settingKey].toObject();
+                    
+                    QJsonArray affectedTypesArray = settingObject[AFFECTED_TYPES_JSON_KEY].toArray();
+                    if (affectedTypesArray.isEmpty()) {
+                        affectedTypesArray = groupObject[AFFECTED_TYPES_JSON_KEY].toArray();
+                    }
+                    
+                    if (affectedTypesArray.contains(queryType)) {
+                        // this is a setting we should include in the responseObject
+                        
+                        // we need to check if the settings map has a value for this setting
+                        QVariant variantValue;
+                        QVariant settingsMapGroupValue = _settingsMap.value(group);
+                        
+                        if (!settingsMapGroupValue.isNull()) {
+                            variantValue = settingsMapGroupValue.toMap().value(settingKey);
+                        }
+                        
+                        if (variantValue.isNull()) {
+                            // no value for this setting, pass the default
+                            groupResponseObject[settingKey] = settingObject[SETTING_DEFAULT_KEY];
+                        } else {
+                            groupResponseObject[settingKey] = QJsonValue::fromVariant(variantValue);
+                        }
+                    }
+                }
+                
+                if (!groupResponseObject.isEmpty()) {
+                    // set this group's object to the constructed object
+                    responseObject[group] = groupResponseObject;
+                }
+            }
+            
+        }
+        
+        connection->respond(HTTPConnection::StatusCode200, QJsonDocument(responseObject).toJson(), "application/json");
+        return true;
+    }
+    
+    return false;
+}
+
+void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject,
+                                                                        QVariantMap& settingsVariant,
+                                                                        QJsonObject descriptionObject) {
+    foreach(const QString& key, postedObject.keys()) {
+
+        QJsonValue rootValue = postedObject[key];
+        
+        // we don't continue if this key is not present in our descriptionObject
+        if (descriptionObject.contains(key)) {
+            if (rootValue.isString()) {
+                settingsVariant[key] = rootValue.toString();
+            } else if (rootValue.isBool()) {
+                settingsVariant[key] = rootValue.toBool();
+            } else if (rootValue.isObject()) {
+                // there's a JSON Object to explore, so attempt to recurse into it
+                QJsonObject nextDescriptionObject = descriptionObject[key].toObject();
+                
+                if (nextDescriptionObject.contains(DESCRIPTION_SETTINGS_KEY)) {
+                    if (!settingsVariant.contains(key)) {
+                        // we don't have a map below this key yet, so set it up now
+                        settingsVariant[key] = QVariantMap();
+                    }
+                    
+                    recurseJSONObjectAndOverwriteSettings(rootValue.toObject(),
+                                                          *reinterpret_cast<QVariantMap*>(settingsVariant[key].data()),
+                                                          nextDescriptionObject[DESCRIPTION_SETTINGS_KEY].toObject());
+                }
+            }
+        }
+    }
+}
+
+QByteArray DomainServerSettingsManager::getJSONSettingsMap() const {
+    return QJsonDocument::fromVariant(_settingsMap).toJson();
+}
+
+void DomainServerSettingsManager::persistToFile() {
+    QFile settingsFile(QCoreApplication::applicationDirPath() + SETTINGS_CONFIG_FILE_RELATIVE_PATH);
+    
+    if (settingsFile.open(QIODevice::WriteOnly)) {
+        settingsFile.write(getJSONSettingsMap());
+    } else {
+        qCritical("Could not write to JSON settings file. Unable to persist settings.");
+    }
+}
\ No newline at end of file
diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h
new file mode 100644
index 0000000000..8b80cad280
--- /dev/null
+++ b/domain-server/src/DomainServerSettingsManager.h
@@ -0,0 +1,35 @@
+//
+//  DomainServerSettingsManager.h
+//  domain-server/src
+//
+//  Created by Stephen Birarda on 2014-06-24.
+//  Copyright 2014 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
+//
+
+#ifndef hifi_DomainServerSettingsManager_h
+#define hifi_DomainServerSettingsManager_h
+
+#include <QtCore/QJsonDocument>
+
+#include <HTTPManager.h>
+
+class DomainServerSettingsManager : public QObject, HTTPRequestHandler {
+    Q_OBJECT
+public:
+    DomainServerSettingsManager();
+    bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url);
+    
+    QByteArray getJSONSettingsMap() const;
+private:
+    void recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, QVariantMap& settingsVariant,
+                                               QJsonObject descriptionObject);
+    void persistToFile();
+    
+    QJsonObject _descriptionObject;
+    QVariantMap _settingsMap;
+};
+
+#endif // hifi_DomainServerSettingsManager_h
\ No newline at end of file
diff --git a/examples/concertCamera.js b/examples/concertCamera.js
new file mode 100644
index 0000000000..03908d0b57
--- /dev/null
+++ b/examples/concertCamera.js
@@ -0,0 +1,72 @@
+//
+//  concertCamera.js
+//
+//  Created by Philip Rosedale on June 24, 2014
+//  Copyright 2014 High Fidelity, Inc.
+//
+//  Move a camera through a series of pre-set locations by pressing number keys
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+var oldMode;
+var avatarPosition;
+
+var cameraNumber = 0;
+var freeCamera = false; 
+
+var cameraLocations = [ {x: 7971.9, y: 241.3, z: 7304.1}, {x: 7973.0, y: 241.3, z: 7304.1}, {x: 7975.5, y: 241.3, z: 7304.1}, {x: 7972.3, y: 241.3, z: 7303.3}, {x: 7971.0, y: 241.3, z: 7304.3}, {x: 7973.5, y: 240.7, z: 7302.5} ];
+var cameraLookAts = [ {x: 7971.1, y: 241.3, z: 7304.1}, {x: 7972.1, y: 241.3, z: 7304.1}, {x: 7972.1, y: 241.3, z: 7304.1}, {x: 7972.1, y: 241.3, z: 7304.1}, {x: 7972.1, y: 241.3, z: 7304.1}, {x: 7971.3, y: 241.3, z: 7304.2} ];
+
+function saveCameraState() {
+    oldMode = Camera.getMode();
+    avatarPosition = MyAvatar.position;
+    Camera.setModeShiftPeriod(0.0);
+    Camera.setMode("independent");
+}
+
+function restoreCameraState() {
+    Camera.stopLooking();
+    Camera.setMode(oldMode);
+}
+
+function update(deltaTime) {
+    if (freeCamera) { 
+        var delta = Vec3.subtract(MyAvatar.position, avatarPosition);
+        if (Vec3.length(delta) > 0.05) {
+            cameraNumber = 0;
+            freeCamera = false;
+            restoreCameraState();
+        }
+    }
+}
+
+function keyPressEvent(event) {
+
+    var choice = parseInt(event.text);
+
+    if ((choice > 0) && (choice <= cameraLocations.length)) {
+        print("camera " + choice);
+               if (!freeCamera) {
+            saveCameraState();
+            freeCamera = true;
+        }
+        Camera.setMode("independent");
+        Camera.setPosition(cameraLocations[choice - 1]);
+        Camera.keepLookingAt(cameraLookAts[choice - 1]);
+    }
+    if (event.text == "ESC") {
+        cameraNumber = 0;
+        freeCamera = false;
+        restoreCameraState();
+    }
+    if (event.text == "0") {
+        //  Show camera location in log
+        var cameraLocation = Camera.getPosition();
+        print(cameraLocation.x + ", " + cameraLocation.y + ", " + cameraLocation.z);
+    }
+}
+
+Script.update.connect(update);
+Controller.keyPressEvent.connect(keyPressEvent);
diff --git a/examples/editModels.js b/examples/editModels.js
index eebcd075fa..64c203534c 100644
--- a/examples/editModels.js
+++ b/examples/editModels.js
@@ -34,6 +34,7 @@ var LASER_COLOR = { red: 255, green: 0, blue: 0 };
 var LASER_LENGTH_FACTOR = 500
 ;
 
+var MIN_ANGULAR_SIZE = 2;
 var MAX_ANGULAR_SIZE = 45;
 
 var LEFT = 0;
@@ -277,8 +278,9 @@ function controller(wichSide) {
         var X = Vec3.sum(A, Vec3.multiply(B, x));
         var d = Vec3.length(Vec3.subtract(P, X));
         
-        if (0 < x && x < LASER_LENGTH_FACTOR) {
-            if (2 * Math.atan(properties.radius / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14 > MAX_ANGULAR_SIZE) {
+        var angularSize = 2 * Math.atan(properties.radius / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14;
+        if (0 < x && angularSize > MIN_ANGULAR_SIZE) {
+            if (angularSize > MAX_ANGULAR_SIZE) {
                 print("Angular size too big: " + 2 * Math.atan(properties.radius / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14);
                 return { valid: false };
             }
@@ -326,7 +328,8 @@ function controller(wichSide) {
                                                           origin: this.palmPosition,
                                                           direction: this.front
                                                           });
-            if (intersection.accurate && intersection.modelID.isKnownID) {
+            var angularSize = 2 * Math.atan(intersection.modelProperties.radius / Vec3.distance(Camera.getPosition(), intersection.modelProperties.position)) * 180 / 3.14;
+            if (intersection.accurate && intersection.modelID.isKnownID && angularSize > MIN_ANGULAR_SIZE && angularSize < MAX_ANGULAR_SIZE) {
                 this.glowedIntersectingModel = intersection.modelID;
                 Models.editModel(this.glowedIntersectingModel, { glowLevel: 0.25 });
             }
@@ -749,7 +752,16 @@ function Tooltip() {
         text += "ID: " + properties.id + "\n"
         text += "model url: " + properties.modelURL + "\n"
         text += "animation url: " + properties.animationURL + "\n"
-        
+        if (properties.sittingPoints.length > 0) {
+            text += properties.sittingPoints.length + " sitting points: "
+            for (var i = 0; i < properties.sittingPoints.length; ++i) {
+                text += properties.sittingPoints[i].name + " "
+            }
+        } else {
+            text += "No sitting points"
+        }
+
+
         Overlays.editOverlay(this.textOverlay, { text: text });
     }
     
@@ -828,8 +840,9 @@ function mousePressEvent(event) {
             var X = Vec3.sum(A, Vec3.multiply(B, x));
             var d = Vec3.length(Vec3.subtract(P, X));
             
-            if (0 < x && x < LASER_LENGTH_FACTOR) {
-                if (2 * Math.atan(properties.radius / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14 < MAX_ANGULAR_SIZE) {
+            var angularSize = 2 * Math.atan(properties.radius / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14;
+            if (0 < x && angularSize > MIN_ANGULAR_SIZE) {
+                if (angularSize < MAX_ANGULAR_SIZE) {
                     modelSelected = true;
                     selectedModelID = foundModel;
                     selectedModelProperties = properties;
@@ -884,7 +897,8 @@ function mouseMoveEvent(event)  {
                 glowedModelID.isKnownID = false;
             }
             
-            if (modelIntersection.modelID.isKnownID) {
+            var angularSize = 2 * Math.atan(modelIntersection.modelProperties.radius / Vec3.distance(Camera.getPosition(), modelIntersection.modelProperties.position)) * 180 / 3.14;
+            if (modelIntersection.modelID.isKnownID && angularSize > MIN_ANGULAR_SIZE && angularSize < MAX_ANGULAR_SIZE) {
                 Models.editModel(modelIntersection.modelID, { glowLevel: 0.25 });
                 glowedModelID = modelIntersection.modelID;
             }
@@ -1114,8 +1128,8 @@ function handeMenuEvent(menuItem){
                 Models.editModel(editModelID, properties);
             }
         }
-        tooltip.show(false);
     }
+    tooltip.show(false);
 }
 Menu.menuItemEvent.connect(handeMenuEvent);
 
diff --git a/examples/editVoxels.js b/examples/editVoxels.js
index cff0d65743..412612fdad 100644
--- a/examples/editVoxels.js
+++ b/examples/editVoxels.js
@@ -1196,7 +1196,7 @@ function menuItemEvent(menuItem) {
             print("deleting...");
             if (isImporting) {
                 cancelImport();
-            } else {
+            } else if (voxelToolSelected) {
                 Clipboard.deleteVoxel(selectedVoxel.x, selectedVoxel.y, selectedVoxel.z, selectedVoxel.s);
             }
         }
diff --git a/examples/inWorldTestTone.js b/examples/inWorldTestTone.js
new file mode 100644
index 0000000000..e4f34d87cd
--- /dev/null
+++ b/examples/inWorldTestTone.js
@@ -0,0 +1,38 @@
+//
+//  inWorldTestTone.js
+//  
+//
+//  Created by Philip Rosedale on 5/29/14.
+//  Copyright 2014 High Fidelity, Inc.
+//
+//  This example script plays a test tone that is useful for debugging audio dropout.  220Hz test tone played at the domain origin.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+var sound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/220Sine.wav");
+
+var soundPlaying = false;
+
+function update(deltaTime) {
+    if (!Audio.isInjectorPlaying(soundPlaying)) {
+        var options = new AudioInjectionOptions();
+        options.position = { x:0, y:0, z:0 };
+        options.volume = 1.0;
+        options.loop = true;
+        soundPlaying = Audio.playSound(sound, options);
+        print("Started sound loop");
+    } 
+}
+
+function scriptEnding() {
+    if (Audio.isInjectorPlaying(soundPlaying)) {
+        Audio.stopInjector(soundPlaying);
+        print("Stopped sound loop");
+    }
+}
+
+Script.update.connect(update);
+Script.scriptEnding.connect(scriptEnding);
+
diff --git a/examples/inspect.js b/examples/inspect.js
index b292d5f609..a4ff405c3f 100644
--- a/examples/inspect.js
+++ b/examples/inspect.js
@@ -195,6 +195,8 @@ function keyReleaseEvent(event) {
     }
 }
 
+
+
 function mousePressEvent(event) {
     if (alt && !isActive) {
         mouseLastX = event.x;
diff --git a/examples/sit.js b/examples/sit.js
index d10c08c95a..056a65fbf1 100644
--- a/examples/sit.js
+++ b/examples/sit.js
@@ -19,7 +19,7 @@ var buttonHeight = 46;
 var buttonPadding = 10;
 
 var buttonPositionX = windowDimensions.x - buttonPadding - buttonWidth;
-var buttonPositionY = (windowDimensions.y - buttonHeight) / 2 ;
+var buttonPositionY = (windowDimensions.y - buttonHeight) / 2 - (buttonHeight + buttonPadding);
 
 var sitDownButton = Overlays.addOverlay("image", {
                                          x: buttonPositionX, y: buttonPositionY, width: buttonWidth, height: buttonHeight,
@@ -38,9 +38,15 @@ var standUpButton = Overlays.addOverlay("image", {
 
 var passedTime = 0.0;
 var startPosition = null;
+var startRotation = null;
 var animationLenght = 2.0;
 
-var sitting = false; 
+var avatarOldPosition = { x: 0, y: 0, z: 0 };
+
+var sitting = false;
+
+var seat = new Object();
+var hiddingSeats = false;
 
 // This is the pose we would like to end up
 var pose = [
@@ -49,13 +55,7 @@ var pose = [
 	{joint:"RightFoot", rotation: {x:30, y:15.0, z:0.0}},
 	{joint:"LeftUpLeg", rotation: {x:100.0, y:-15.0, z:0.0}},
 	{joint:"LeftLeg", rotation: {x:-130.0, y:-15.0, z:0.0}},
-	{joint:"LeftFoot", rotation: {x:30, y:15.0, z:0.0}},
-
-	{joint:"Spine2", rotation: {x:20, y:0.0, z:0.0}},
-	
-	{joint:"RightShoulder", rotation: {x:0.0, y:40.0, z:0.0}},
-	{joint:"LeftShoulder", rotation: {x:0.0, y:-40.0, z:0.0}}
-
+	{joint:"LeftFoot", rotation: {x:30, y:15.0, z:0.0}}
 ];
 
 var startPoseAndTransition = [];
@@ -89,7 +89,7 @@ var sittingDownAnimation = function(deltaTime) {
 	}
 }
 
-var standingUpAnimation = function(deltaTime){
+var standingUpAnimation = function(deltaTime) {
 
 	passedTime += deltaTime;
 	var factor = 1 - passedTime/animationLenght;
@@ -103,6 +103,24 @@ var standingUpAnimation = function(deltaTime){
 	}
 }
 
+var goToSeatAnimation = function(deltaTime) {
+    passedTime += deltaTime;
+	var factor = passedTime/animationLenght;
+    
+	if (passedTime <= animationLenght) {
+		var targetPosition = Vec3.sum(seat.position, { x: 0.3, y: 0.5, z: 0 });
+		MyAvatar.position = Vec3.sum(Vec3.multiply(startPosition, 1 - factor), Vec3.multiply(targetPosition, factor));
+	} else if (passedTime <= 2 * animationLenght) {
+        Quat.print("MyAvatar: ", MyAvatar.orientation);
+        Quat.print("Seat: ", seat.rotation);
+        MyAvatar.orientation = Quat.mix(startRotation, seat.rotation, factor - 1);
+    } else {
+        Script.update.disconnect(goToSeatAnimation);
+        sitDown();
+        showIndicators(false);
+    }
+}
+
 function sitDown() {
 	sitting = true;
 	passedTime = 0.0;
@@ -130,15 +148,104 @@ function standUp() {
 	Overlays.editOverlay(sitDownButton, { visible: true });
 }
 
-Controller.mousePressEvent.connect(function(event){
+var models = new Object();
+function SeatIndicator(modelProperties, seatIndex) {
+    this.position =  Vec3.sum(modelProperties.position,
+                              Vec3.multiply(Vec3.multiplyQbyV(modelProperties.modelRotation,
+                                                              modelProperties.sittingPoints[seatIndex].position),
+                                            modelProperties.radius));
+                              
+    this.orientation = Quat.multiply(modelProperties.modelRotation,
+                                     modelProperties.sittingPoints[seatIndex].rotation);
+    this.scale = MyAvatar.scale / 12;
+    
+    this.sphere = Overlays.addOverlay("sphere", {
+                                    position: this.position,
+                                    size: this.scale,
+                                    solid: true,
+                                    color: { red: 0, green: 0, blue: 255 },
+                                    alpha: 0.3,
+                                    visible: true
+                                    });
+    
+    this.show = function(doShow) {
+        Overlays.editOverlay(this.sphere, { visible: doShow });
+    }
+    
+    this.update = function() {
+        Overlays.editOverlay(this.sphere, {
+                             position: this.position,
+                             size: this.scale
+                             });
+    }
+    
+    this.cleanup = function() {
+        Overlays.deleteOverlay(this.sphere);
+    }
+}
 
+Controller.mousePressEvent.connect(function(event) {
 	var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
 
 	if (clickedOverlay == sitDownButton) {
 		sitDown();
 	} else if (clickedOverlay == standUpButton) {
 		standUp();
-	}
+    } else {
+       var pickRay = Camera.computePickRay(event.x, event.y);
+                                   
+       var clickedOnSeat = false;
+       
+       for (index in models) {
+           var model = models[index];
+           
+           for (var i = 0; i < model.properties.sittingPoints.length; ++i) {
+               if (raySphereIntersection(pickRay.origin,
+                                         pickRay.direction,
+                                         model.properties.sittingPoints[i].indicator.position,
+                                         model.properties.sittingPoints[i].indicator.scale / 2)) {
+                   clickedOnSeat = true;
+                   seat.position = model.properties.sittingPoints[i].indicator.position;
+                   seat.rotation = model.properties.sittingPoints[i].indicator.orientation;
+               }
+           }
+       }
+       if (clickedOnSeat) {
+           passedTime = 0.0;
+           startPosition = MyAvatar.position;
+           startRotation = MyAvatar.orientation;
+           try{ Script.update.disconnect(standingUpAnimation); } catch(e){}
+           try{ Script.update.disconnect(sittingDownAnimation); } catch(e){}
+           Script.update.connect(goToSeatAnimation);
+       }
+       
+                                   
+                                   
+                                   return;
+       var intersection = Models.findRayIntersection(pickRay);
+       
+       if (intersection.accurate && intersection.intersects && false) {
+           var properties = intersection.modelProperties;
+           print("Intersecting with model, let's check for seats.");
+           
+           if (properties.sittingPoints.length > 0) {
+                print("Available seats, going to the first one: " + properties.sittingPoints[0].name);
+                seat.position = Vec3.sum(properties.position, Vec3.multiplyQbyV(properties.modelRotation, properties.sittingPoints[0].position));
+                Vec3.print("Seat position: ", seat.position);
+                seat.rotation = Quat.multiply(properties.modelRotation, properties.sittingPoints[0].rotation);
+                Quat.print("Seat rotation: ", seat.rotation);
+                                   
+                passedTime = 0.0;
+                startPosition = MyAvatar.position;
+                startRotation = MyAvatar.orientation;
+                try{ Script.update.disconnect(standingUpAnimation); } catch(e){}
+                try{ Script.update.disconnect(sittingDownAnimation); } catch(e){}
+                Script.update.connect(goToSeatAnimation);
+           } else {
+               print ("Sorry, no seats here.");
+           }
+       }
+   }
 })
 
 function update(deltaTime){
@@ -149,7 +256,76 @@ function update(deltaTime){
 		var newY = (windowDimensions.y - buttonHeight) / 2 ;
 		Overlays.editOverlay( standUpButton, {x: newX, y: newY} );
 		Overlays.editOverlay( sitDownButton, {x: newX, y: newY} );
-	}		
+	}
+	
+    if (MyAvatar.position.x != avatarOldPosition.x &&
+        MyAvatar.position.y != avatarOldPosition.y &&
+        MyAvatar.position.z != avatarOldPosition.z) {
+        avatarOldPosition = MyAvatar.position;
+        
+        var SEARCH_RADIUS = 5;
+        var foundModels = Models.findModels(MyAvatar.position, SEARCH_RADIUS);
+        // Let's remove indicator that got out of radius
+        for (model in models) {
+            if (Vec3.distance(models[model].properties.position, MyAvatar.position) > SEARCH_RADIUS) {
+                removeIndicators(models[model]);
+            }
+        }
+        
+        // Let's add indicators to new seats in radius
+        for (var i = 0; i < foundModels.length; ++i) {
+            var model = foundModels[i];
+            if (typeof(models[model.id]) == "undefined") {
+                addIndicators(model);
+            }
+        }
+        
+        if (hiddingSeats && passedTime >= animationLenght) {
+            showIndicators(true);
+        }
+    }
+}
+
+function addIndicators(modelID) {
+    modelID.properties = Models.getModelProperties(modelID);
+    if (modelID.properties.sittingPoints.length > 0) {
+        for (var i = 0; i < modelID.properties.sittingPoints.length; ++i) {
+            modelID.properties.sittingPoints[i].indicator = new SeatIndicator(modelID.properties, i);
+        }
+        
+        models[modelID.id] = modelID;
+    } else {
+        Models.editModel(modelID, { glowLevel: 0.0 });
+    }
+}
+
+function removeIndicators(modelID) {
+    for (var i = 0; i < modelID.properties.sittingPoints.length; ++i) {
+        modelID.properties.sittingPoints[i].indicator.cleanup();
+    }
+    delete models[modelID.id];
+}
+
+function showIndicators(doShow) {
+    for (model in models) {
+        var modelID = models[model];
+        for (var i = 0; i < modelID.properties.sittingPoints.length; ++i) {
+            modelID.properties.sittingPoints[i].indicator.show(doShow);
+        }
+    }
+    hiddingSeats = !doShow;
+}
+
+function raySphereIntersection(origin, direction, center, radius) {
+    var A = origin;
+    var B = Vec3.normalize(direction);
+    var P = center;
+    
+    var x = Vec3.dot(Vec3.subtract(P, A), B);
+    var X = Vec3.sum(A, Vec3.multiply(B, x));
+    var d = Vec3.length(Vec3.subtract(P, X));
+    
+    return (x > 0 && d <= radius);
 }
 
 function keyPressEvent(event) {
@@ -167,11 +343,15 @@ Script.update.connect(update);
 Controller.keyPressEvent.connect(keyPressEvent);
 
 Script.scriptEnding.connect(function() {
-
 	for (var i = 0; i < pose.length; i++){
 		    MyAvatar.clearJointData(pose[i].joint);
-	}		
+	}
 
 	Overlays.deleteOverlay(sitDownButton);
 	Overlays.deleteOverlay(standUpButton);
+    for (model in models){
+        for (var i = 0; i < models[model].properties.sittingPoints.length; ++i) {
+            models[model].properties.sittingPoints[i].indicator.cleanup();
+        }
+    }
 });
diff --git a/examples/squeezeHands.js b/examples/squeezeHands.js
index e53dd9569c..da720734e1 100644
--- a/examples/squeezeHands.js
+++ b/examples/squeezeHands.js
@@ -9,8 +9,8 @@
 //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
 //
 
-var rightHandAnimation = "https://s3-us-west-1.amazonaws.com/highfidelity-public/animations/RightHandAnim.fbx";
-var leftHandAnimation = "https://s3-us-west-1.amazonaws.com/highfidelity-public/animations/LeftHandAnim.fbx";
+var rightHandAnimation = "https://s3-us-west-1.amazonaws.com/highfidelity-public/animations/RightHandAnimPhilip.fbx";
+var leftHandAnimation = "https://s3-us-west-1.amazonaws.com/highfidelity-public/animations/LeftHandAnimPhilip.fbx";
 
 var LEFT = 0;
 var RIGHT = 1;
diff --git a/examples/toyball.js b/examples/toyball.js
index d312c1bc94..e03fd67a5d 100644
--- a/examples/toyball.js
+++ b/examples/toyball.js
@@ -26,14 +26,21 @@ var RIGHT_TIP = 3;
 var RIGHT_BUTTON_FWD = 11;
 var RIGHT_BUTTON_3 = 9;
 
+var BALL_RADIUS = 0.08;
+var GRAVITY_STRENGTH = 0.5;
+
+var HELD_COLOR = { red: 240, green: 0, blue: 0 };
+var THROWN_COLOR = { red: 128, green: 0, blue: 0 };
+
 var leftBallAlreadyInHand = false;
 var rightBallAlreadyInHand = false;
 var leftHandParticle;
 var rightHandParticle;
 
-var throwSound = new Sound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/throw.raw");
+var newSound = new Sound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/throw.raw");
 var catchSound = new Sound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/catch.raw");
-var targetRadius = 0.25;
+var throwSound = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Switches%20and%20sliders/slider%20-%20whoosh1.raw");
+var targetRadius = 1.0;
 
 
 var wantDebugging = false;
@@ -44,31 +51,19 @@ function debugPrint(message) {
 }
 
 function getBallHoldPosition(whichSide) { 
-    var normal;
-    var tipPosition;
     if (whichSide == LEFT_PALM) {
-        normal = Controller.getSpatialControlNormal(LEFT_PALM);
-        tipPosition = Controller.getSpatialControlPosition(LEFT_TIP);
+        position = MyAvatar.getLeftPalmPosition();
     } else {
-        normal = Controller.getSpatialControlNormal(RIGHT_PALM);
-        tipPosition = Controller.getSpatialControlPosition(RIGHT_TIP);
+        position = MyAvatar.getRightPalmPosition();
     }
     
-    var BALL_FORWARD_OFFSET = 0.08; // put the ball a bit forward of fingers
-    position = { x: BALL_FORWARD_OFFSET * normal.x,
-                 y: BALL_FORWARD_OFFSET * normal.y, 
-                 z: BALL_FORWARD_OFFSET * normal.z }; 
-
-    position.x += tipPosition.x;
-    position.y += tipPosition.y;
-    position.z += tipPosition.z;
-    
     return position;
 }
 
 function checkControllerSide(whichSide) {
     var BUTTON_FWD;
     var BUTTON_3;
+    var TRIGGER;
     var palmPosition;
     var ballAlreadyInHand;
     var handMessage;
@@ -76,18 +71,20 @@ function checkControllerSide(whichSide) {
     if (whichSide == LEFT_PALM) {
         BUTTON_FWD = LEFT_BUTTON_FWD;
         BUTTON_3 = LEFT_BUTTON_3;
+        TRIGGER = 0;
         palmPosition = Controller.getSpatialControlPosition(LEFT_PALM);
         ballAlreadyInHand = leftBallAlreadyInHand;
         handMessage = "LEFT";
     } else {
         BUTTON_FWD = RIGHT_BUTTON_FWD;
         BUTTON_3 = RIGHT_BUTTON_3;
+        TRIGGER = 1;
         palmPosition = Controller.getSpatialControlPosition(RIGHT_PALM);
         ballAlreadyInHand = rightBallAlreadyInHand;
         handMessage = "RIGHT";
     }
-    
-    var grabButtonPressed = (Controller.isButtonPressed(BUTTON_FWD) || Controller.isButtonPressed(BUTTON_3));
+
+    var grabButtonPressed = (Controller.isButtonPressed(BUTTON_FWD) || Controller.isButtonPressed(BUTTON_3) || (Controller.getTriggerValue(TRIGGER) > 0.5));
 
     // If I don't currently have a ball in my hand, then try to catch closest one
     if (!ballAlreadyInHand && grabButtonPressed) {
@@ -107,8 +104,11 @@ function checkControllerSide(whichSide) {
             var ballPosition = getBallHoldPosition(whichSide);
             var properties = { position: { x: ballPosition.x, 
                                            y: ballPosition.y, 
-                                           z: ballPosition.z }, 
-                                velocity : { x: 0, y: 0, z: 0}, inHand: true };
+                                           z: ballPosition.z },
+                                           color: HELD_COLOR, 
+                                velocity : { x: 0, y: 0, z: 0}, 
+                                lifetime : 600,
+                                inHand: true };
             Particles.editParticle(closestParticle, properties);
             
     		var options = new AudioInjectionOptions();
@@ -127,7 +127,7 @@ function checkControllerSide(whichSide) {
     //}
 
     //  If '3' is pressed, and not holding a ball, make a new one
-    if (Controller.isButtonPressed(BUTTON_3) && !ballAlreadyInHand) {
+    if (grabButtonPressed && !ballAlreadyInHand) {
         var ballPosition = getBallHoldPosition(whichSide);
         var properties = { position: { x: ballPosition.x, 
                                        y: ballPosition.y, 
@@ -135,11 +135,11 @@ function checkControllerSide(whichSide) {
                 velocity: { x: 0, y: 0, z: 0}, 
                 gravity: { x: 0, y: 0, z: 0}, 
                 inHand: true,
-                radius: 0.05,
+                radius: BALL_RADIUS,
                 damping: 0.999,
-                color: { red: 255, green: 0, blue: 0 },
+                color: HELD_COLOR,
 
-                lifetime: 10 // 10 seconds - same as default, not needed but here as an example
+                lifetime: 600 // 10 seconds - same as default, not needed but here as an example
             };
 
         newParticle = Particles.addParticle(properties);
@@ -155,7 +155,7 @@ function checkControllerSide(whichSide) {
         var options = new AudioInjectionOptions();
         options.position = ballPosition;
         options.volume = 1.0;
-        Audio.playSound(catchSound, options);
+        Audio.playSound(newSound, options);
         
         return; // exit early
     }
@@ -188,7 +188,9 @@ function checkControllerSide(whichSide) {
                                 y: tipVelocity.y * THROWN_VELOCITY_SCALING, 
                                 z: tipVelocity.z * THROWN_VELOCITY_SCALING } ,
                     inHand: false,
-                    gravity: { x: 0, y: -2, z: 0}, 
+                    color: THROWN_COLOR,
+                    lifetime: 10,
+                    gravity: { x: 0, y: -GRAVITY_STRENGTH, z: 0}, 
                 };
 
             Particles.editParticle(handParticle, properties);
diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt
index 44fd8fd5d1..1821216f28 100644
--- a/interface/CMakeLists.txt
+++ b/interface/CMakeLists.txt
@@ -185,7 +185,11 @@ if (LIBOVR_FOUND AND NOT DISABLE_LIBOVR)
       set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${LIBOVR_INCLUDE_DIRS}")
     endif ()
     
-    target_link_libraries(${TARGET_NAME} "${LIBOVR_LIBRARIES}")
+    if (WIN32)
+      target_link_libraries(${TARGET_NAME} optimized "${LIBOVR_RELEASE_LIBRARIES}" debug "${LIBOVR_DEBUG_LIBRARIES}")
+    else()
+      target_link_libraries(${TARGET_NAME} "${LIBOVR_LIBRARIES}")
+    endif()
 endif (LIBOVR_FOUND AND NOT DISABLE_LIBOVR)
 
 # and with PrioVR library
diff --git a/interface/external/oculus/readme.txt b/interface/external/oculus/readme.txt
index 002047b73f..f689f81478 100644
--- a/interface/external/oculus/readme.txt
+++ b/interface/external/oculus/readme.txt
@@ -2,18 +2,12 @@
 Instructions for adding the Oculus library (LibOVR) to Interface
 Stephen Birarda, March 6, 2014
 
-You can download the Oculus SDK from https://developer.oculusvr.com/ (account creation required). Interface has been tested with SDK version 0.2.5.
+You can download the Oculus SDK from https://developer.oculusvr.com/ (account creation required). Interface has been tested with SDK version 0.3.2.
 
 1. Copy the Oculus SDK folders from the LibOVR directory (Lib, Include, Src) into the interface/externals/oculus folder.
    This readme.txt should be there as well.
    
    You may optionally choose to copy the SDK folders to a location outside the repository (so you can re-use with different checkouts and different projects). 
    If so our CMake find module expects you to set the ENV variable 'HIFI_LIB_DIR' to a directory containing a subfolder 'oculus' that contains the three folders mentioned above.
-   
-   NOTE: On OS X there is a linker error with version 0.2.5c of the Oculus SDK.
-   It must be re-built (from the included LibOVR_With_Samples.xcodeproj) with RRTI support.
-   In XCode Build Settings for the ovr target, set "Enable C++ Runtime Types" to yes.
-   Then, Archive and use the organizer to save a copy of the built products.
-   In the exported directory you will have a new libovr.a to copy into the oculus directory from above.
 
 2. Clear your build directory, run cmake and build, and you should be all set.
\ No newline at end of file
diff --git a/interface/resources/shaders/oculus.frag b/interface/resources/shaders/oculus.frag
index f2b066a974..8e96428e17 100644
--- a/interface/resources/shaders/oculus.frag
+++ b/interface/resources/shaders/oculus.frag
@@ -4,12 +4,11 @@
 //  oculus.frag
 //  fragment shader
 //
-//  Created by Andrzej Kapolka on 11/26/13.
-//  Copyright 2013 High Fidelity, Inc.
+//  Created by Ben Arnold on 6/24/14.
+//  Copyright 2014 High Fidelity, Inc.
 //
-//  this shader is an adaptation (HLSL -> GLSL, removed conditional) of the one in the Oculus sample
-//  code (Samples/OculusRoomTiny/RenderTiny_D3D1X_Device.cpp), which is under the Apache license
-//  (http://www.apache.org/licenses/LICENSE-2.0)
+//  this shader is an adaptation (HLSL -> GLSL) of the one in the
+//  Oculus_SDK_Overview.pdf for the 3.2 SDK.
 //
 //  Distributed under the Apache License, Version 2.0.
 //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@@ -17,23 +16,16 @@
 
 uniform sampler2D texture;
 
-uniform vec2 lensCenter;
-uniform vec2 screenCenter;
-uniform vec2 scale;
-uniform vec2 scaleIn;
-uniform vec4 hmdWarpParam;
-
-vec2 hmdWarp(vec2 in01) {
-    vec2 theta = (in01 - lensCenter) * scaleIn;
-    float rSq = theta.x * theta.x + theta.y * theta.y;
-    vec2 theta1 = theta * (hmdWarpParam.x + hmdWarpParam.y * rSq +
-    hmdWarpParam.z * rSq * rSq + hmdWarpParam.w * rSq * rSq * rSq);
-    return lensCenter + scale * theta1;
-}
+varying float vFade;
+varying vec2 oTexCoord0;
+varying vec2 oTexCoord1;
+varying vec2 oTexCoord2;
 
 void main(void) {
-    vec2 tc = hmdWarp(gl_TexCoord[0].st);
-    vec2 below = step(screenCenter.st + vec2(-0.25, -0.5), tc.st);
-    vec2 above = vec2(1.0, 1.0) - step(screenCenter.st + vec2(0.25, 0.5), tc.st);
-    gl_FragColor = mix(vec4(0.0, 0.0, 0.0, 1.0), texture2D(texture, tc), above.s * above.t * below.s * below.t);
+    // 3 samples for fixing chromatic aberrations
+    float r = texture2D(texture, oTexCoord0.xy).r;
+    float g = texture2D(texture, oTexCoord1.xy).g;
+    float b = texture2D(texture, oTexCoord2.xy).b;
+    
+    gl_FragColor = vec4(r * vFade, g * vFade, b * vFade, 1.0);
 }
diff --git a/interface/resources/shaders/oculus.vert b/interface/resources/shaders/oculus.vert
new file mode 100644
index 0000000000..800fbd9317
--- /dev/null
+++ b/interface/resources/shaders/oculus.vert
@@ -0,0 +1,63 @@
+#version 120
+
+//
+//  oculus.vert
+//  vertex shader
+//
+//  Created by Ben Arnold on 6/24/14.
+//  Copyright 2014 High Fidelity, Inc.
+//
+//  this shader is an adaptation (HLSL -> GLSL) of the one in the
+//  Oculus_SDK_Overview.pdf for the 3.2 SDK.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+uniform vec2 EyeToSourceUVScale;
+uniform vec2 EyeToSourceUVOffset;
+uniform mat4 EyeRotationStart;
+uniform mat4 EyeRotationEnd;
+
+attribute vec2 position;
+attribute vec4 color;
+attribute vec2 texCoord0;
+attribute vec2 texCoord1;
+attribute vec2 texCoord2;
+
+varying float vFade;
+varying vec2 oTexCoord0;
+varying vec2 oTexCoord1;
+varying vec2 oTexCoord2;
+
+vec2 TimewarpTexCoord(vec2 texCoord, mat4 rotMat)
+{
+    // Vertex inputs are in TanEyeAngle space for the R,G,B channels (i.e. after chromatic
+    // aberration and distortion). These are now "real world" vectors in direction (x,y,1)
+    // relative to the eye of the HMD. Apply the 3x3 timewarp rotation to these vectors.
+    vec3 transformed = vec3( rotMat * vec4(texCoord.xy, 1, 1) );
+    
+    // Project them back onto the Z=1 plane of the rendered images.
+    vec2 flattened = (transformed.xy / transformed.z);
+    
+    // Scale them into ([0,0.5],[0,1]) or ([0.5,0],[0,1]) UV lookup space (depending on eye)
+    return (EyeToSourceUVScale * flattened + EyeToSourceUVOffset);
+}
+
+void main()
+{
+    float timewarpMixFactor = color.a;
+    mat4 mixedEyeRot = EyeRotationStart * (1.0 - timewarpMixFactor) + EyeRotationEnd * (timewarpMixFactor);
+    
+    oTexCoord0 = TimewarpTexCoord(texCoord0, mixedEyeRot);
+    oTexCoord1 = TimewarpTexCoord(texCoord1, mixedEyeRot);
+    oTexCoord2 = TimewarpTexCoord(texCoord2, mixedEyeRot);
+    
+    //Flip y texture coordinates
+    oTexCoord0.y = 1.0 - oTexCoord0.y;
+    oTexCoord1.y = 1.0 - oTexCoord1.y;
+    oTexCoord2.y = 1.0 - oTexCoord2.y;
+    
+    gl_Position = vec4(position.xy, 0.5, 1.0);
+    vFade = color.r; // For vignette fade
+}
\ No newline at end of file
diff --git a/interface/resources/styles/preferences.qss b/interface/resources/styles/preferences.qss
index e678acd0c9..40e35c8e52 100644
--- a/interface/resources/styles/preferences.qss
+++ b/interface/resources/styles/preferences.qss
@@ -12,7 +12,8 @@ QLabel#advancedTuningLabel {
 
 QPushButton#buttonBrowseHead,
 QPushButton#buttonBrowseBody,
-QPushButton#buttonBrowseLocation {
+QPushButton#buttonBrowseLocation,
+QPushButton#buttonBrowseScriptsLocation {
     background-image: url(styles/search.svg);
     background-repeat: no-repeat;
     background-position: center center;
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 8e7f20b76f..b9e70b0854 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -60,6 +60,7 @@
 #include <ParticlesScriptingInterface.h>
 #include <PerfStat.h>
 #include <ResourceCache.h>
+#include <UserActivityLogger.h>
 #include <UUID.h>
 #include <OctreeSceneStats.h>
 #include <LocalVoxelsList.h>
@@ -136,7 +137,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
         _nodeThread(new QThread(this)),
         _datagramProcessor(),
         _frameCount(0),
-        _fps(120.0f),
+        _fps(60.0f),
         _justStarted(true),
         _voxelImporter(NULL),
         _importSucceded(false),
@@ -169,7 +170,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
         _nodeBoundsDisplay(this),
         _previousScriptLocation(),
         _applicationOverlay(),
-        _runningScriptsWidget(new RunningScriptsWidget(_window)),
+        _runningScriptsWidget(NULL),
         _runningScriptsWidgetWasVisible(false),
         _trayIcon(new QSystemTrayIcon(_window)),
         _lastNackTime(usecTimestampNow())
@@ -203,6 +204,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
     // call Menu getInstance static method to set up the menu
     _window->setMenuBar(Menu::getInstance());
 
+    _runningScriptsWidget = new RunningScriptsWidget(_window);
+
     unsigned int listenPort = 0; // bind to an ephemeral port by default
     const char** constArgv = const_cast<const char**>(argv);
     const char* portStr = getCmdOption(argc, constArgv, "--listenPort");
@@ -268,6 +271,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
 
     // set the account manager's root URL and trigger a login request if we don't have the access token
     accountManager.setAuthURL(DEFAULT_NODE_AUTH_URL);
+    UserActivityLogger::getInstance().launch(applicationVersion());
 
     // once the event loop has started, check and signal for an access token
     QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection);
@@ -396,7 +400,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
 }
 
 Application::~Application() {
-
+    int DELAY_TIME = 1000;
+    UserActivityLogger::getInstance().close(DELAY_TIME);
+    
     qInstallMessageHandler(NULL);
 
     // make sure we don't call the idle timer any more
@@ -553,7 +559,7 @@ void Application::initializeGL() {
     }
 
     // update before the first render
-    update(0.0f);
+    update(1.f / _fps);
 
     InfoView::showFirstTime();
 }
@@ -565,6 +571,16 @@ void Application::paintGL() {
     bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
     PerformanceWarning warn(showWarnings, "Application::paintGL()");
 
+    const bool glowEnabled = Menu::getInstance()->isOptionChecked(MenuOption::EnableGlowEffect);
+
+    // Set the desired FBO texture size. If it hasn't changed, this does nothing.
+    // Otherwise, it must rebuild the FBOs
+    if (OculusManager::isConnected()) {
+        _textureCache.setFrameBufferSize(OculusManager::getRenderTargetSize());
+    } else {
+        _textureCache.setFrameBufferSize(_glWidget->size());
+    }
+
     glEnable(GL_LINE_SMOOTH);
 
     if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) {
@@ -573,28 +589,16 @@ void Application::paintGL() {
         _myCamera.setTargetRotation(_myAvatar->getHead()->getCameraOrientation());
 
     } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
+        //Note, the camera distance is set in Camera::setMode() so we dont have to do it here.
         _myCamera.setTightness(0.0f);     //  Camera is directly connected to head without smoothing
         _myCamera.setTargetPosition(_myAvatar->getUprightHeadPosition());
-        _myCamera.setTargetRotation(_myAvatar->getHead()->getCameraOrientation());
+        _myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation());
 
     } else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
         _myCamera.setTightness(0.0f);
-        glm::vec3 eyePosition = _myAvatar->getHead()->calculateAverageEyePosition();
-        float headHeight = eyePosition.y - _myAvatar->getPosition().y;
         _myCamera.setDistance(MIRROR_FULLSCREEN_DISTANCE * _scaleMirror);
-        _myCamera.setTargetPosition(_myAvatar->getPosition() + glm::vec3(0, headHeight + (_raiseMirror * _myAvatar->getScale()), 0));
         _myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)));
-    }
-
-    if (OculusManager::isConnected()) {
-        // Oculus in third person causes nausea, so only allow it if option is checked in dev menu
-        if (!Menu::getInstance()->isOptionChecked(MenuOption::AllowOculusCameraModeChange) || _myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) {
-            _myCamera.setDistance(0.0f);
-            _myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition());
-            _myCamera.setTargetRotation(_myAvatar->getHead()->getCameraOrientation());
-        }
-        _myCamera.setUpShift(0.0f);
-        _myCamera.setTightness(0.0f);     //  Camera is directly connected to head without smoothing
+        _myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition());
     }
 
     // Update camera position
@@ -629,16 +633,32 @@ void Application::paintGL() {
         updateShadowMap();
     }
 
+    //If we aren't using the glow shader, we have to clear the color and depth buffer
+    if (!glowEnabled) {
+        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+    }
+
     if (OculusManager::isConnected()) {
-        OculusManager::display(whichCamera);
+        //When in mirror mode, use camera rotation. Otherwise, use body rotation
+        if (whichCamera.getMode() == CAMERA_MODE_MIRROR) {
+            OculusManager::display(whichCamera.getRotation(), whichCamera.getPosition(), whichCamera);
+        } else {
+            OculusManager::display(_myAvatar->getWorldAlignedOrientation(), whichCamera.getPosition(), whichCamera);
+        }
 
     } else if (TV3DManager::isConnected()) {
-        _glowEffect.prepare();
+        if (glowEnabled) {
+            _glowEffect.prepare();
+        }
         TV3DManager::display(whichCamera);
-        _glowEffect.render();
+        if (glowEnabled) {
+            _glowEffect.render();
+        }
 
     } else {
-        _glowEffect.prepare();
+        if (glowEnabled) {
+            _glowEffect.prepare();
+        }
 
         glMatrixMode(GL_MODELVIEW);
         glPushMatrix();
@@ -646,7 +666,9 @@ void Application::paintGL() {
         displaySide(whichCamera);
         glPopMatrix();
 
-        _glowEffect.render();
+        if (glowEnabled) {
+            _glowEffect.render();
+        }
 
         if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) {
             renderRearViewMirror(_mirrorViewRect);
@@ -2178,7 +2200,8 @@ int Application::sendNackPackets() {
             OctreeSceneStats& stats = _octreeServerSceneStats[nodeUUID];
 
             // make copy of missing sequence numbers from stats
-            const QSet<OCTREE_PACKET_SEQUENCE> missingSequenceNumbers = stats.getMissingSequenceNumbers();
+            const QSet<OCTREE_PACKET_SEQUENCE> missingSequenceNumbers =
+                stats.getIncomingOctreeSequenceNumberStats().getMissingSet();
 
             _octreeSceneStatsLock.unlock();
 
@@ -3148,9 +3171,7 @@ void Application::resetSensors() {
     _faceshift.reset();
     _visage.reset();
 
-    if (OculusManager::isConnected()) {
-        OculusManager::reset();
-    }
+    OculusManager::reset();
 
     _prioVR.reset();
     //_leapmotion.reset();
@@ -3303,6 +3324,10 @@ void Application::nodeKilled(SharedNodePointer node) {
     _particleEditSender.nodeKilled(node);
     _modelEditSender.nodeKilled(node);
 
+    if (node->getType() == NodeType::AudioMixer) {
+        QMetaObject::invokeMethod(&_audio, "resetIncomingMixedAudioSequenceNumberStats");
+    }
+
     if (node->getType() == NodeType::VoxelServer) {
         QUuid nodeUUID = node->getUUID();
         // see if this is the first we've heard of this node...
@@ -3547,10 +3572,12 @@ void Application::saveScripts() {
     _settings->endArray();
 }
 
-ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScriptFromEditor) {
+ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScriptFromEditor, bool activateMainWindow) {
     QUrl scriptUrl(scriptName);
     const QString& scriptURLString = scriptUrl.toString();
-    if(loadScriptFromEditor && _scriptEnginesHash.contains(scriptURLString) && !_scriptEnginesHash[scriptURLString]->isFinished()){
+    if (_scriptEnginesHash.contains(scriptURLString) && loadScriptFromEditor
+        && !_scriptEnginesHash[scriptURLString]->isFinished()) {
+
         return _scriptEnginesHash[scriptURLString];
     }
 
@@ -3563,11 +3590,13 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
 
         if (!scriptEngine->hasScript()) {
             qDebug() << "Application::loadScript(), script failed to load...";
+            QMessageBox::warning(getWindow(), "Error Loading Script", scriptURLString + " failed to load.");
             return NULL;
         }
 
-        _scriptEnginesHash.insert(scriptURLString, scriptEngine);
+        _scriptEnginesHash.insertMulti(scriptURLString, scriptEngine);
         _runningScriptsWidget->setRunningScripts(getRunningScripts());
+        UserActivityLogger::getInstance().loadedScript(scriptURLString);
     }
 
     // setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so
@@ -3623,13 +3652,16 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
     // when the application is about to quit, stop our script engine so it unwinds properly
     connect(this, SIGNAL(aboutToQuit()), scriptEngine, SLOT(stop()));
 
+    NodeList* nodeList = NodeList::getInstance();
+    connect(nodeList, &NodeList::nodeKilled, scriptEngine, &ScriptEngine::nodeKilled);
+
     scriptEngine->moveToThread(workerThread);
 
     // Starts an event loop, and emits workerThread->started()
     workerThread->start();
 
     // restore the main window's active state
-    if (!loadScriptFromEditor) {
+    if (activateMainWindow && !loadScriptFromEditor) {
         _window->activateWindow();
     }
     bumpSettings();
@@ -3638,7 +3670,10 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
 }
 
 void Application::scriptFinished(const QString& scriptName) {
-    if (_scriptEnginesHash.remove(scriptName)) {
+    const QString& scriptURLString = QUrl(scriptName).toString();
+    QHash<QString, ScriptEngine*>::iterator it = _scriptEnginesHash.find(scriptURLString);
+    if (it != _scriptEnginesHash.end()) {
+        _scriptEnginesHash.erase(it);
         _runningScriptsWidget->scriptStopped(scriptName);
         _runningScriptsWidget->setRunningScripts(getRunningScripts());
         bumpSettings();
@@ -3658,8 +3693,9 @@ void Application::stopAllScripts(bool restart) {
 }
 
 void Application::stopScript(const QString &scriptName) {
-    if (_scriptEnginesHash.contains(scriptName)) {
-        _scriptEnginesHash.value(scriptName)->stop();
+    const QString& scriptURLString = QUrl(scriptName).toString();
+    if (_scriptEnginesHash.contains(scriptURLString)) {
+        _scriptEnginesHash.value(scriptURLString)->stop();
         qDebug() << "stopping script..." << scriptName;
     }
 }
diff --git a/interface/src/Application.h b/interface/src/Application.h
index af0dfe9d15..56b6f673ae 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -324,7 +324,7 @@ public slots:
     void loadScriptURLDialog();
     void toggleLogDialog();
     void initAvatarAndViewFrustum();
-    ScriptEngine* loadScript(const QString& fileNameString = QString(), bool loadScriptFromEditor = false);
+    ScriptEngine* loadScript(const QString& fileNameString = QString(), bool loadScriptFromEditor = false, bool activateMainWindow = false);
     void scriptFinished(const QString& scriptName);
     void stopAllScripts(bool restart = false);
     void stopScript(const QString& scriptName);
diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp
index 271bcd5279..f5f148969c 100644
--- a/interface/src/Audio.cpp
+++ b/interface/src/Audio.cpp
@@ -102,7 +102,9 @@ Audio::Audio(int16_t initialJitterBufferSamples, QObject* parent) :
     _samplesPerScope(NETWORK_SAMPLES_PER_FRAME * _framesPerScope),
     _scopeInput(0),
     _scopeOutputLeft(0),
-    _scopeOutputRight(0)
+    _scopeOutputRight(0),
+    _audioMixerAvatarStreamStats(),
+    _outgoingAvatarAudioSequenceNumber(0)
 {
     // clear the array of locally injected samples
     memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL);
@@ -118,6 +120,9 @@ void Audio::init(QGLWidget *parent) {
 
 void Audio::reset() {
     _ringBuffer.reset();
+    _outgoingAvatarAudioSequenceNumber = 0;
+    _audioMixerInjectedStreamStatsMap.clear();
+    _incomingMixedAudioSequenceNumberStats.reset();
 }
 
 QAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName) {
@@ -218,9 +223,14 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
             hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &pv);
             pPropertyStore->Release();
             pPropertyStore = NULL;
-            //QAudio devices seems to only take the 31 first characters of the Friendly Device Name.
-            const DWORD QT_WIN_MAX_AUDIO_DEVICENAME_LEN = 31;
-            deviceName = QString::fromWCharArray((wchar_t*)pv.pwszVal).left(QT_WIN_MAX_AUDIO_DEVICENAME_LEN);
+            deviceName = QString::fromWCharArray((wchar_t*)pv.pwszVal);
+            const DWORD WINDOWS7_MAJOR_VERSION = 6;
+            const DWORD WINDOWS7_MINOR_VERSION = 1;
+            if (osvi.dwMajorVersion <= WINDOWS7_MAJOR_VERSION && osvi.dwMinorVersion <= WINDOWS7_MINOR_VERSION) {
+                // Windows 7 provides only the 31 first characters of the device name.
+                const DWORD QT_WIN7_MAX_AUDIO_DEVICENAME_LEN = 31;
+                deviceName = deviceName.left(QT_WIN7_MAX_AUDIO_DEVICENAME_LEN);
+            }
             qDebug() << (mode == QAudio::AudioOutput ? "output" : "input") << " device:" << deviceName;
             PropVariantClear(&pv);
         }
@@ -416,7 +426,7 @@ void Audio::handleAudioInput() {
     static char audioDataPacket[MAX_PACKET_SIZE];
 
     static int numBytesPacketHeader = numBytesForPacketHeaderGivenPacketType(PacketTypeMicrophoneAudioNoEcho);
-    static int leadingBytes = numBytesPacketHeader + sizeof(glm::vec3) + sizeof(glm::quat) + sizeof(quint8);
+    static int leadingBytes = numBytesPacketHeader + sizeof(quint16) + sizeof(glm::vec3) + sizeof(glm::quat) + sizeof(quint8);
 
     static int16_t* networkAudioSamples = (int16_t*) (audioDataPacket + leadingBytes);
 
@@ -461,8 +471,8 @@ void Audio::handleAudioInput() {
         int16_t* inputAudioSamples = new int16_t[inputSamplesRequired];
         _inputRingBuffer.readSamples(inputAudioSamples, inputSamplesRequired);
         
-        int numNetworkBytes = _isStereoInput ? NETWORK_BUFFER_LENGTH_BYTES_STEREO : NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL;
-        int numNetworkSamples = _isStereoInput ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL;
+        const int numNetworkBytes = _isStereoInput ? NETWORK_BUFFER_LENGTH_BYTES_STEREO : NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL;
+        const int numNetworkSamples = _isStereoInput ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL;
 
         // zero out the monoAudioSamples array and the locally injected audio
         memset(networkAudioSamples, 0, numNetworkBytes);
@@ -634,12 +644,10 @@ void Audio::handleAudioInput() {
                 packetType = PacketTypeSilentAudioFrame;
                 
                 // we need to indicate how many silent samples this is to the audio mixer
-                audioDataPacket[0] = _isStereoInput
-                    ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO
-                    : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL;
+                networkAudioSamples[0] = numNetworkSamples;
                 numAudioBytes = sizeof(int16_t);
             } else {
-                numAudioBytes = _isStereoInput ? NETWORK_BUFFER_LENGTH_BYTES_STEREO : NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL;
+                numAudioBytes = numNetworkBytes;
                 
                 if (Menu::getInstance()->isOptionChecked(MenuOption::EchoServerAudio)) {
                     packetType = PacketTypeMicrophoneAudioWithEcho;
@@ -650,6 +658,10 @@ void Audio::handleAudioInput() {
 
             char* currentPacketPtr = audioDataPacket + populatePacketHeader(audioDataPacket, packetType);
             
+            // pack sequence number
+            memcpy(currentPacketPtr, &_outgoingAvatarAudioSequenceNumber, sizeof(quint16));
+            currentPacketPtr += sizeof(quint16);
+
             // set the mono/stereo byte
             *currentPacketPtr++ = isStereo;
 
@@ -662,6 +674,7 @@ void Audio::handleAudioInput() {
             currentPacketPtr += sizeof(headOrientation);
             
             nodeList->writeDatagram(audioDataPacket, numAudioBytes + leadingBytes, audioMixer);
+            _outgoingAvatarAudioSequenceNumber++;
 
             Application::getInstance()->getBandwidthMeter()->outputStream(BandwidthMeter::AUDIO)
                 .updateValue(numAudioBytes + leadingBytes);
@@ -704,6 +717,36 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) {
     Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::AUDIO).updateValue(audioByteArray.size());
 }
 
+void Audio::parseAudioStreamStatsPacket(const QByteArray& packet) {
+
+    int numBytesPacketHeader = numBytesForPacketHeader(packet);
+    const char* dataAt = packet.constData() + numBytesPacketHeader;
+
+    // parse the appendFlag, clear injected audio stream stats if 0
+    quint8 appendFlag = *(reinterpret_cast<const quint16*>(dataAt));
+    dataAt += sizeof(quint8);
+    if (!appendFlag) {
+        _audioMixerInjectedStreamStatsMap.clear();
+    }
+
+    // parse the number of stream stats structs to follow
+    quint16 numStreamStats = *(reinterpret_cast<const quint16*>(dataAt));
+    dataAt += sizeof(quint16);
+
+    // parse the stream stats
+    AudioStreamStats streamStats;
+    for (quint16 i = 0; i < numStreamStats; i++) {
+        memcpy(&streamStats, dataAt, sizeof(AudioStreamStats));
+        dataAt += sizeof(AudioStreamStats);
+
+        if (streamStats._streamType == PositionalAudioRingBuffer::Microphone) {
+            _audioMixerAvatarStreamStats = streamStats;
+        } else {
+            _audioMixerInjectedStreamStatsMap[streamStats._streamIdentifier] = streamStats;
+        }
+    }
+}
+
 // NOTE: numSamples is the total number of single channel samples, since callers will always call this with stereo
 // data we know that we will have 2x samples for each stereo time sample at the format's sample rate
 void Audio::addSpatialAudioToBuffer(unsigned int sampleTime, const QByteArray& spatialAudio, unsigned int numSamples) {
@@ -803,6 +846,16 @@ void Audio::toggleStereoInput() {
 }
 
 void Audio::processReceivedAudio(const QByteArray& audioByteArray) {
+
+    QUuid senderUUID = uuidFromPacketHeader(audioByteArray);
+
+    // parse sequence number for this packet
+    int numBytesPacketHeader = numBytesForPacketHeader(audioByteArray);
+    const char* sequenceAt = audioByteArray.constData() + numBytesPacketHeader;
+    quint16 sequence = *((quint16*)sequenceAt);
+    _incomingMixedAudioSequenceNumberStats.sequenceNumberReceived(sequence, senderUUID);
+
+    // parse audio data
     _ringBuffer.parseData(audioByteArray);
     
     float networkOutputToOutputRatio = (_desiredOutputFormat.sampleRate() / (float) _outputFormat.sampleRate())
@@ -825,7 +878,8 @@ void Audio::processReceivedAudio(const QByteArray& audioByteArray) {
         QByteArray outputBuffer;
         outputBuffer.resize(numDeviceOutputSamples * sizeof(int16_t));
         
-        int numSamplesNeededToStartPlayback = NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (_jitterBufferSamples * 2);
+        int numSamplesNeededToStartPlayback = std::min(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (_jitterBufferSamples * 2),
+            _ringBuffer.getSampleCapacity());
         
         if (!_ringBuffer.isNotStarvedOrHasMinimumSamples(numSamplesNeededToStartPlayback)) {
             //  We are still waiting for enough samples to begin playback
@@ -842,6 +896,7 @@ void Audio::processReceivedAudio(const QByteArray& audioByteArray) {
                 buffer.resize(numNetworkOutputSamples * sizeof(int16_t));
 
                 _ringBuffer.readSamples((int16_t*)buffer.data(), numNetworkOutputSamples);
+
                 // Accumulate direct transmission of audio from sender to receiver
                 if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingIncludeOriginal)) {
                     emit preProcessOriginalInboundAudio(sampleTime, buffer, _desiredOutputFormat);
diff --git a/interface/src/Audio.h b/interface/src/Audio.h
index 74fc373cb0..9f04e5cb03 100644
--- a/interface/src/Audio.h
+++ b/interface/src/Audio.h
@@ -16,6 +16,7 @@
 #include <vector>
 
 #include "InterfaceConfig.h"
+#include "AudioStreamStats.h"
 
 #include <QAudio>
 #include <QAudioInput>
@@ -72,13 +73,17 @@ public:
 
     bool getProcessSpatialAudio() const { return _processSpatialAudio; }
 
+    const SequenceNumberStats& getIncomingMixedAudioSequenceNumberStats() const { return _incomingMixedAudioSequenceNumberStats; }
+
 public slots:
     void start();
     void stop();
     void addReceivedAudioToBuffer(const QByteArray& audioByteArray);
+    void parseAudioStreamStatsPacket(const QByteArray& packet);
     void addSpatialAudioToBuffer(unsigned int sampleTime, const QByteArray& spatialAudio, unsigned int numSamples);
     void handleAudioInput();
     void reset();
+    void resetIncomingMixedAudioSequenceNumberStats() { _incomingMixedAudioSequenceNumberStats.reset(); }
     void toggleMute();
     void toggleAudioNoiseReduction();
     void toggleToneInjection();
@@ -102,6 +107,9 @@ public slots:
     float getInputVolume() const { return (_audioInput) ? _audioInput->volume() : 0.0f; }
     void setInputVolume(float volume) { if (_audioInput) _audioInput->setVolume(volume); }
 
+    const AudioStreamStats& getAudioMixerAvatarStreamStats() const { return _audioMixerAvatarStreamStats; }
+    const QHash<QUuid, AudioStreamStats>& getAudioMixerInjectedStreamStatsMap() const { return _audioMixerInjectedStreamStatsMap; }
+
 signals:
     bool muteToggled();
     void preProcessOriginalInboundAudio(unsigned int sampleTime, QByteArray& samples, const QAudioFormat& format);
@@ -233,6 +241,11 @@ private:
     QByteArray* _scopeOutputLeft;
     QByteArray* _scopeOutputRight;
 
+    AudioStreamStats _audioMixerAvatarStreamStats;
+    QHash<QUuid, AudioStreamStats> _audioMixerInjectedStreamStatsMap;
+
+    quint16 _outgoingAvatarAudioSequenceNumber;
+    SequenceNumberStats _incomingMixedAudioSequenceNumberStats;
 };
 
 
diff --git a/interface/src/Camera.cpp b/interface/src/Camera.cpp
index 0e33e14f32..4490b60fc9 100644
--- a/interface/src/Camera.cpp
+++ b/interface/src/Camera.cpp
@@ -317,8 +317,6 @@ void CameraScriptableObject::setMode(const QString& mode) {
     }
     if (currentMode != targetMode) {
         _camera->setMode(targetMode);
-        const float DEFAULT_MODE_SHIFT_PERIOD = 0.5f; // half second
-        _camera->setModeShiftPeriod(DEFAULT_MODE_SHIFT_PERIOD);
     }
 }
 
diff --git a/interface/src/Camera.h b/interface/src/Camera.h
index 5e189c1111..2bbbf0e751 100644
--- a/interface/src/Camera.h
+++ b/interface/src/Camera.h
@@ -42,9 +42,8 @@ public:
     void setTargetPosition(const glm::vec3& t);
     void setTightness(float t) { _tightness = t; }
     void setTargetRotation(const glm::quat& rotation);
-    
-    void setMode(CameraMode m);
     void setModeShiftPeriod(float r);
+    void setMode(CameraMode m);
     void setFieldOfView(float f);
     void setAspectRatio(float a);
     void setNearClip(float n);
@@ -130,6 +129,7 @@ public:
 public slots:
     QString getMode() const;
     void setMode(const QString& mode);
+    void setModeShiftPeriod(float r) {_camera->setModeShiftPeriod(r); }
     void setPosition(const glm::vec3& value) { _camera->setTargetPosition(value);}
 
     glm::vec3 getPosition() const { return _camera->getPosition(); }
diff --git a/interface/src/DatagramProcessor.cpp b/interface/src/DatagramProcessor.cpp
index 29528da126..a159af7be5 100644
--- a/interface/src/DatagramProcessor.cpp
+++ b/interface/src/DatagramProcessor.cpp
@@ -51,7 +51,10 @@ void DatagramProcessor::processDatagrams() {
                     QMetaObject::invokeMethod(&application->_audio, "addReceivedAudioToBuffer", Qt::QueuedConnection,
                                               Q_ARG(QByteArray, incomingPacket));
                     break;
-                    
+                case PacketTypeAudioStreamStats:
+                    QMetaObject::invokeMethod(&application->_audio, "parseAudioStreamStatsPacket", Qt::QueuedConnection,
+                        Q_ARG(QByteArray, incomingPacket));
+                    break;
                 case PacketTypeParticleAddResponse:
                     // this will keep creatorTokenIDs to IDs mapped correctly
                     Particle::handleAddParticleResponse(incomingPacket);
diff --git a/interface/src/GLCanvas.cpp b/interface/src/GLCanvas.cpp
index 49085e63df..17026b5d5c 100644
--- a/interface/src/GLCanvas.cpp
+++ b/interface/src/GLCanvas.cpp
@@ -12,6 +12,7 @@
 #include "Application.h"
 
 #include "GLCanvas.h"
+#include "devices/OculusManager.h"
 #include <QMimeData>
 #include <QUrl>
 #include <QMainWindow>
@@ -41,8 +42,17 @@ void GLCanvas::initializeGL() {
 
 void GLCanvas::paintGL() {
     if (!_throttleRendering && !Application::getInstance()->getWindow()->isMinimized()) {
+        //Need accurate frame timing for the oculus rift
+        if (OculusManager::isConnected()) {
+            OculusManager::beginFrameTiming();
+        }
+
         Application::getInstance()->paintGL();
         swapBuffers();
+
+        if (OculusManager::isConnected()) {
+            OculusManager::endFrameTiming();
+        }
     }
 }
 
@@ -102,8 +112,17 @@ void GLCanvas::activeChanged(Qt::ApplicationState state) {
 void GLCanvas::throttleRender() {
     _frameTimer.start(_idleRenderInterval);
     if (!Application::getInstance()->getWindow()->isMinimized()) {
+        //Need accurate frame timing for the oculus rift
+        if (OculusManager::isConnected()) {
+            OculusManager::beginFrameTiming();
+        }
+
         Application::getInstance()->paintGL();
         swapBuffers();
+
+        if (OculusManager::isConnected()) {
+            OculusManager::endFrameTiming();
+        }
     }
 }
 
diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index 5c8c2e97aa..402347c5d4 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -109,7 +109,8 @@ Menu::Menu() :
     _loginAction(NULL),
     _preferencesDialog(NULL),
     _loginDialog(NULL),
-    _snapshotsLocation()
+    _snapshotsLocation(),
+    _scriptsLocation()
 {
     Application *appInstance = Application::getInstance();
 
@@ -249,12 +250,21 @@ Menu::Menu() :
 
     QMenu* viewMenu = addMenu("View");
 
+#ifdef Q_OS_MAC
     addCheckableActionToQMenuAndActionHash(viewMenu,
                                            MenuOption::Fullscreen,
                                            Qt::CTRL | Qt::META | Qt::Key_F,
                                            false,
                                            appInstance,
                                            SLOT(setFullscreen(bool)));
+#else
+    addCheckableActionToQMenuAndActionHash(viewMenu,
+                                           MenuOption::Fullscreen,
+                                           Qt::CTRL | Qt::Key_F,
+                                           false,
+                                           appInstance,
+                                           SLOT(setFullscreen(bool)));
+#endif
     addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FirstPerson, Qt::Key_P, true,
                                             appInstance,SLOT(cameraMenuChanged()));
     addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, Qt::SHIFT | Qt::Key_H, true);
@@ -322,6 +332,8 @@ Menu::Menu() :
 
     addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Stars, Qt::Key_Asterisk, true);
     addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere, Qt::SHIFT | Qt::Key_A, true);
+
+    addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::EnableGlowEffect, 0, true);
     addActionToQMenuAndActionHash(renderOptionsMenu,
                                   MenuOption::GlowMode,
                                   0,
@@ -336,6 +348,7 @@ Menu::Menu() :
 
     addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Metavoxels, 0, true);
     addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::BuckyBalls, 0, false);
+    addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::StringHair, 0, false);
     addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Particles, 0, true);
     addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, this, SLOT(lodTools()));
 
@@ -366,7 +379,7 @@ Menu::Menu() :
     addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderSkeletonCollisionShapes);
     addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderHeadCollisionShapes);
     addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderBoundingCollisionShapes);
-    addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::CollideAsRagDoll);
+    addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::CollideAsRagdoll);
 
     addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::LookAtVectors, 0, false);
     addCheckableActionToQMenuAndActionHash(avatarOptionsMenu,
@@ -389,7 +402,6 @@ Menu::Menu() :
     addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, false);
 
     QMenu* oculusOptionsMenu = developerMenu->addMenu("Oculus Options");
-    addCheckableActionToQMenuAndActionHash(oculusOptionsMenu, MenuOption::AllowOculusCameraModeChange, 0, false);
     addCheckableActionToQMenuAndActionHash(oculusOptionsMenu, MenuOption::DisplayOculusOverlays, 0, true);
 
     QMenu* sixenseOptionsMenu = developerMenu->addMenu("Sixense Options");
@@ -598,6 +610,7 @@ void Menu::loadSettings(QSettings* settings) {
     _boundaryLevelAdjust = loadSetting(settings, "boundaryLevelAdjust", 0);
     _snapshotsLocation = settings->value("snapshotsLocation",
                                          QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)).toString();
+    setScriptsLocation(settings->value("scriptsLocation", QString()).toString());
 
     settings->beginGroup("View Frustum Offset Camera");
     // in case settings is corrupt or missing loadSetting() will check for NaN
@@ -642,6 +655,7 @@ void Menu::saveSettings(QSettings* settings) {
     settings->setValue("avatarLODDistanceMultiplier", _avatarLODDistanceMultiplier);
     settings->setValue("boundaryLevelAdjust", _boundaryLevelAdjust);
     settings->setValue("snapshotsLocation", _snapshotsLocation);
+    settings->setValue("scriptsLocation", _scriptsLocation);
     settings->beginGroup("View Frustum Offset Camera");
     settings->setValue("viewFrustumOffsetYaw", _viewFrustumOffset.yaw);
     settings->setValue("viewFrustumOffsetPitch", _viewFrustumOffset.pitch);
@@ -1293,6 +1307,7 @@ void Menu::showChat() {
         if (_chatWindow->isHidden()) {
             _chatWindow->show();
         }
+        _chatWindow->activateWindow();
     } else {
         Application::getInstance()->getTrayIcon()->showMessage("Interface", "You need to login to be able to chat with others on this domain.");
     }
@@ -1776,3 +1791,8 @@ QString Menu::getSnapshotsLocation() const {
     }
     return _snapshotsLocation;
 }
+
+void Menu::setScriptsLocation(const QString& scriptsLocation) {
+    _scriptsLocation = scriptsLocation;
+    emit scriptLocationChanged(scriptsLocation);
+}
diff --git a/interface/src/Menu.h b/interface/src/Menu.h
index 4d2174a448..a15d3712f1 100644
--- a/interface/src/Menu.h
+++ b/interface/src/Menu.h
@@ -102,6 +102,9 @@ public:
     QString getSnapshotsLocation() const;
     void setSnapshotsLocation(QString snapshotsLocation) { _snapshotsLocation = snapshotsLocation; }
 
+    const QString& getScriptsLocation() const { return _scriptsLocation; }
+    void setScriptsLocation(const QString& scriptsLocation);
+
     BandwidthDialog* getBandwidthDialog() const { return _bandwidthDialog; }
     FrustumDrawMode getFrustumDrawMode() const { return _frustumDrawMode; }
     ViewFrustumOffset getViewFrustumOffset() const { return _viewFrustumOffset; }
@@ -156,6 +159,9 @@ public:
     void static goToDomain(const QString newDomain);
     void static goTo(QString destination);
 
+signals:
+    void scriptLocationChanged(const QString& newPath);
+
 public slots:
 
     void loginForCurrentDomain();
@@ -283,12 +289,12 @@ private:
     QPointer<LoginDialog> _loginDialog;
     QAction* _chatAction;
     QString _snapshotsLocation;
+    QString _scriptsLocation;
 };
 
 namespace MenuOption {
     const QString AboutApp = "About Interface";
     const QString AlignForearmsWithWrists = "Align Forearms with Wrists";
-    const QString AllowOculusCameraModeChange = "Allow Oculus Camera Mode Change (Nausea)";
     const QString AlternateIK = "Alternate IK";
     const QString AmbientOcclusion = "Ambient Occlusion";
     const QString Animations = "Animations...";
@@ -319,10 +325,11 @@ namespace MenuOption {
     const QString Bandwidth = "Bandwidth Display";
     const QString BandwidthDetails = "Bandwidth Details";
     const QString BuckyBalls = "Bucky Balls";
+    const QString StringHair = "String Hair";
     const QString CascadedShadows = "Cascaded";
     const QString Chat = "Chat...";
     const QString ChatCircling = "Chat Circling";
-    const QString CollideAsRagDoll = "Collide As RagDoll";
+    const QString CollideAsRagdoll = "Collide As Ragdoll";
     const QString CollideWithAvatars = "Collide With Avatars";
     const QString CollideWithEnvironment = "Collide With World Boundaries";
     const QString CollideWithParticles = "Collide With Particles";
@@ -344,6 +351,7 @@ namespace MenuOption {
     const QString DontFadeOnVoxelServerChanges = "Don't Fade In/Out on Voxel Server Changes";
     const QString EchoLocalAudio = "Echo Local Audio";
     const QString EchoServerAudio = "Echo Server Audio";
+    const QString EnableGlowEffect = "Enable Glow Effect (Warning: Poor Oculus Performance)";
     const QString Enable3DTVMode = "Enable 3DTV Mode";
     const QString EnableVRMode = "Enable VR Mode";
     const QString ExpandMiscAvatarTiming = "Expand Misc MyAvatar Timing";
diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp
index 78c97c1703..7a5119a62d 100644
--- a/interface/src/MetavoxelSystem.cpp
+++ b/interface/src/MetavoxelSystem.cpp
@@ -35,6 +35,8 @@ MetavoxelSystem::MetavoxelSystem() :
 }
 
 void MetavoxelSystem::init() {
+    MetavoxelClientManager::init();
+    
     if (!_program.isLinked()) {
         _program.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/metavoxel_point.vert");
         _program.link();
@@ -43,62 +45,19 @@ void MetavoxelSystem::init() {
     }
     _buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw);
     _buffer.create();
-    
-    connect(NodeList::getInstance(), SIGNAL(nodeAdded(SharedNodePointer)), SLOT(maybeAttachClient(const SharedNodePointer&)));
 }
 
-SharedObjectPointer MetavoxelSystem::findFirstRaySpannerIntersection(
-        const glm::vec3& origin, const glm::vec3& direction, const AttributePointer& attribute, float& distance) {
-   SharedObjectPointer closestSpanner;
-   float closestDistance = FLT_MAX;
-   foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
-        if (node->getType() == NodeType::MetavoxelServer) {
-            QMutexLocker locker(&node->getMutex());
-            MetavoxelClient* client = static_cast<MetavoxelClient*>(node->getLinkedData());
-            if (client) {
-                float clientDistance;
-                SharedObjectPointer clientSpanner = client->getData().findFirstRaySpannerIntersection(
-                    origin, direction, attribute, clientDistance);
-                if (clientSpanner && clientDistance < closestDistance) {
-                    closestSpanner = clientSpanner;
-                    closestDistance = clientDistance;
-                }
-            }
-        }
-    }
-    if (closestSpanner) {
-        distance = closestDistance;
-    }
-    return closestSpanner;
-}
-
-void MetavoxelSystem::applyEdit(const MetavoxelEditMessage& edit, bool reliable) {
-    foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
-        if (node->getType() == NodeType::MetavoxelServer) {
-            QMutexLocker locker(&node->getMutex());
-            MetavoxelClient* client = static_cast<MetavoxelClient*>(node->getLinkedData());
-            if (client) {
-                client->applyEdit(edit, reliable);
-            }
-        }
-    }
+MetavoxelLOD MetavoxelSystem::getLOD() const {
+    const float FIXED_LOD_THRESHOLD = 0.01f;
+    return MetavoxelLOD(Application::getInstance()->getCamera()->getPosition(), FIXED_LOD_THRESHOLD);
 }
 
 void MetavoxelSystem::simulate(float deltaTime) {
-    // simulate the clients
+    // update the clients
     _points.clear();
     _simulateVisitor.setDeltaTime(deltaTime);
     _simulateVisitor.setOrder(-Application::getInstance()->getViewFrustum()->getDirection());
-    foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
-        if (node->getType() == NodeType::MetavoxelServer) {
-            QMutexLocker locker(&node->getMutex());
-            MetavoxelClient* client = static_cast<MetavoxelClient*>(node->getLinkedData());
-            if (client) {
-                client->simulate(deltaTime);
-                client->guide(_simulateVisitor);
-            }
-        }
-    }
+    update();
     
     _buffer.bind();
     int bytes = _points.size() * sizeof(Point);
@@ -153,7 +112,7 @@ void MetavoxelSystem::render() {
     foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
         if (node->getType() == NodeType::MetavoxelServer) {
             QMutexLocker locker(&node->getMutex());
-            MetavoxelClient* client = static_cast<MetavoxelClient*>(node->getLinkedData());
+            MetavoxelSystemClient* client = static_cast<MetavoxelSystemClient*>(node->getLinkedData());
             if (client) {
                 client->guide(_renderVisitor);
             }
@@ -161,11 +120,13 @@ void MetavoxelSystem::render() {
     }
 }
 
-void MetavoxelSystem::maybeAttachClient(const SharedNodePointer& node) {
-    if (node->getType() == NodeType::MetavoxelServer) {
-        QMutexLocker locker(&node->getMutex());
-        node->setLinkedData(new MetavoxelClient(NodeList::getInstance()->nodeWithUUID(node->getUUID())));
-    }
+MetavoxelClient* MetavoxelSystem::createClient(const SharedNodePointer& node) {
+    return new MetavoxelSystemClient(node, this);
+}
+
+void MetavoxelSystem::updateClient(MetavoxelClient* client) {
+    MetavoxelClientManager::updateClient(client);
+    client->guide(_simulateVisitor);
 }
 
 MetavoxelSystem::SimulateVisitor::SimulateVisitor(QVector<Point>& points) :
@@ -235,115 +196,22 @@ bool MetavoxelSystem::RenderVisitor::visit(Spanner* spanner, const glm::vec3& cl
     return true;
 }
 
-MetavoxelClient::MetavoxelClient(const SharedNodePointer& node) :
-    _node(node),
-    _sequencer(byteArrayWithPopulatedHeader(PacketTypeMetavoxelData)) {
-    
-    connect(&_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendData(const QByteArray&)));
-    connect(&_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readPacket(Bitstream&)));
-    connect(&_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(clearSendRecordsBefore(int)));
-    connect(&_sequencer, SIGNAL(receiveAcknowledged(int)), SLOT(clearReceiveRecordsBefore(int)));
-    
-    // insert the baseline send record
-    SendRecord sendRecord = { 0 };
-    _sendRecords.append(sendRecord);
-    
-    // insert the baseline receive record
-    ReceiveRecord receiveRecord = { 0, _data };
-    _receiveRecords.append(receiveRecord);
+MetavoxelSystemClient::MetavoxelSystemClient(const SharedNodePointer& node, MetavoxelSystem* system) :
+    MetavoxelClient(node, system) {
 }
 
-MetavoxelClient::~MetavoxelClient() {
-    // close the session
-    Bitstream& out = _sequencer.startPacket();
-    out << QVariant::fromValue(CloseSessionMessage());
-    _sequencer.endPacket();
-}
-
-static MetavoxelLOD getLOD() {
-    const float FIXED_LOD_THRESHOLD = 0.01f;
-    return MetavoxelLOD(Application::getInstance()->getCamera()->getPosition(), FIXED_LOD_THRESHOLD);
-}
-
-void MetavoxelClient::guide(MetavoxelVisitor& visitor) {
-    visitor.setLOD(getLOD());
-    _data.guide(visitor);
-}
-
-void MetavoxelClient::applyEdit(const MetavoxelEditMessage& edit, bool reliable) {
-    if (reliable) {
-        _sequencer.getReliableOutputChannel()->sendMessage(QVariant::fromValue(edit));
-    
-    } else {
-        // apply immediately to local tree
-        edit.apply(_data, _sequencer.getWeakSharedObjectHash());
-
-        // start sending it out
-        _sequencer.sendHighPriorityMessage(QVariant::fromValue(edit));
-    }
-}
-
-void MetavoxelClient::simulate(float deltaTime) {
-    Bitstream& out = _sequencer.startPacket();
-    
-    ClientStateMessage state = { getLOD() };
-    out << QVariant::fromValue(state);
-    _sequencer.endPacket();
-    
-    // record the send
-    SendRecord record = { _sequencer.getOutgoingPacketNumber(), state.lod };
-    _sendRecords.append(record);
-}
-
-int MetavoxelClient::parseData(const QByteArray& packet) {
+int MetavoxelSystemClient::parseData(const QByteArray& packet) {
     // process through sequencer
     QMetaObject::invokeMethod(&_sequencer, "receivedDatagram", Q_ARG(const QByteArray&, packet));
     Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::METAVOXELS).updateValue(packet.size());
     return packet.size();
 }
 
-void MetavoxelClient::sendData(const QByteArray& data) {
+void MetavoxelSystemClient::sendDatagram(const QByteArray& data) {
     NodeList::getInstance()->writeDatagram(data, _node);
     Application::getInstance()->getBandwidthMeter()->outputStream(BandwidthMeter::METAVOXELS).updateValue(data.size());
 }
 
-void MetavoxelClient::readPacket(Bitstream& in) {
-    QVariant message;
-    in >> message;
-    handleMessage(message, in);
-    
-    // record the receipt
-    ReceiveRecord record = { _sequencer.getIncomingPacketNumber(), _data, _sendRecords.first().lod };
-    _receiveRecords.append(record);
-    
-    // reapply local edits
-    foreach (const DatagramSequencer::HighPriorityMessage& message, _sequencer.getHighPriorityMessages()) {
-        if (message.data.userType() == MetavoxelEditMessage::Type) {
-            message.data.value<MetavoxelEditMessage>().apply(_data, _sequencer.getWeakSharedObjectHash());
-        }
-    }
-}
-
-void MetavoxelClient::clearSendRecordsBefore(int index) {
-    _sendRecords.erase(_sendRecords.begin(), _sendRecords.begin() + index + 1);
-}
-
-void MetavoxelClient::clearReceiveRecordsBefore(int index) {
-    _receiveRecords.erase(_receiveRecords.begin(), _receiveRecords.begin() + index + 1);
-}
-
-void MetavoxelClient::handleMessage(const QVariant& message, Bitstream& in) {
-    int userType = message.userType();
-    if (userType == MetavoxelDeltaMessage::Type) {
-        _data.readDelta(_receiveRecords.first().data, _receiveRecords.first().lod, in, _sendRecords.first().lod);
-        
-    } else if (userType == QMetaType::QVariantList) {
-        foreach (const QVariant& element, message.toList()) {
-            handleMessage(element, in);
-        }
-    }
-}
-
 static void enableClipPlane(GLenum plane, float x, float y, float z, float w) {
     GLdouble coefficients[] = { x, y, z, w };
     glClipPlane(plane, coefficients);
diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h
index f98a260ab1..f3a4fd4412 100644
--- a/interface/src/MetavoxelSystem.h
+++ b/interface/src/MetavoxelSystem.h
@@ -18,37 +18,31 @@
 
 #include <glm/glm.hpp>
 
-#include <NodeList.h>
-
-#include <DatagramSequencer.h>
-#include <MetavoxelData.h>
-#include <MetavoxelMessages.h>
+#include <MetavoxelClientManager.h>
 
 #include "renderer/ProgramObject.h"
 
 class Model;
 
 /// Renders a metavoxel tree.
-class MetavoxelSystem : public QObject {
+class MetavoxelSystem : public MetavoxelClientManager {
     Q_OBJECT
 
 public:
 
     MetavoxelSystem();
 
-    void init();
-    
-    SharedObjectPointer findFirstRaySpannerIntersection(const glm::vec3& origin, const glm::vec3& direction,
-        const AttributePointer& attribute, float& distance);
-    
-    Q_INVOKABLE void applyEdit(const MetavoxelEditMessage& edit, bool reliable = false);
+    virtual void init();
+
+    virtual MetavoxelLOD getLOD() const;
     
     void simulate(float deltaTime);
     void render();
-    
-private slots:
 
-    void maybeAttachClient(const SharedNodePointer& node);
+protected:
+
+    virtual MetavoxelClient* createClient(const SharedNodePointer& node);
+    virtual void updateClient(MetavoxelClient* client); 
 
 private:
     
@@ -89,59 +83,18 @@ private:
 };
 
 /// A client session associated with a single server.
-class MetavoxelClient : public NodeData {
+class MetavoxelSystemClient : public MetavoxelClient {
     Q_OBJECT    
     
 public:
     
-    MetavoxelClient(const SharedNodePointer& node);
-    virtual ~MetavoxelClient();
-
-    MetavoxelData& getData() { return _data; }
-
-    void guide(MetavoxelVisitor& visitor);
-
-    void applyEdit(const MetavoxelEditMessage& edit, bool reliable = false);
-
-    void simulate(float deltaTime);
-
+    MetavoxelSystemClient(const SharedNodePointer& node, MetavoxelSystem* system);
+    
     virtual int parseData(const QByteArray& packet);
 
-private slots:
+protected:
     
-    void sendData(const QByteArray& data);
-
-    void readPacket(Bitstream& in);
-    
-    void clearSendRecordsBefore(int index);
-    
-    void clearReceiveRecordsBefore(int index);
-    
-private:
-    
-    void handleMessage(const QVariant& message, Bitstream& in);
-    
-    class SendRecord {
-    public:
-        int packetNumber;
-        MetavoxelLOD lod;
-    };
-    
-    class ReceiveRecord {
-    public:
-        int packetNumber;
-        MetavoxelData data;
-        MetavoxelLOD lod;
-    };
-    
-    SharedNodePointer _node;
-    
-    DatagramSequencer _sequencer;
-    
-    MetavoxelData _data;
-    
-    QList<SendRecord> _sendRecords;
-    QList<ReceiveRecord> _receiveRecords;
+    virtual void sendDatagram(const QByteArray& data);
 };
 
 /// Base class for spanner renderers; provides clipping.
diff --git a/interface/src/ScriptsModel.cpp b/interface/src/ScriptsModel.cpp
new file mode 100644
index 0000000000..f9ed94f3fa
--- /dev/null
+++ b/interface/src/ScriptsModel.cpp
@@ -0,0 +1,209 @@
+//
+//  ScriptsModel.cpp
+//  interface/src
+//
+//  Created by Ryan Huffman on 05/12/14.
+//  Copyright 2014 High Fidelity, Inc.
+//
+//  S3 request code written with ModelBrowser as a reference.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include <QNetworkAccessManager>
+#include <QUrl>
+#include <QUrlQuery>
+#include <QXmlStreamReader>
+
+#include "ScriptsModel.h"
+#include "Menu.h"
+
+
+static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com";
+static const QString PUBLIC_URL = "http://public.highfidelity.io";
+static const QString MODELS_LOCATION = "scripts/";
+
+static const QString PREFIX_PARAMETER_NAME = "prefix";
+static const QString MARKER_PARAMETER_NAME = "marker";
+static const QString IS_TRUNCATED_NAME = "IsTruncated";
+static const QString CONTAINER_NAME = "Contents";
+static const QString KEY_NAME = "Key";
+
+static const int SCRIPT_PATH = Qt::UserRole;
+
+ScriptItem::ScriptItem(const QString& filename, const QString& fullPath) :
+    _filename(filename),
+    _fullPath(fullPath) {
+};
+
+ScriptsModel::ScriptsModel(QObject* parent) :
+    QAbstractListModel(parent),
+    _loadingScripts(false),
+    _localDirectory(),
+    _fsWatcher(),
+    _localFiles(),
+    _remoteFiles() {
+
+    QString scriptPath = Menu::getInstance()->getScriptsLocation();
+
+    _localDirectory.setPath(scriptPath);
+    _localDirectory.setFilter(QDir::Files | QDir::Readable);
+    _localDirectory.setNameFilters(QStringList("*.js"));
+
+    _fsWatcher.addPath(_localDirectory.absolutePath());
+    connect(&_fsWatcher, &QFileSystemWatcher::directoryChanged, this, &ScriptsModel::reloadLocalFiles);
+
+    connect(Menu::getInstance(), &Menu::scriptLocationChanged, this, &ScriptsModel::updateScriptsLocation);
+
+    reloadLocalFiles();
+    reloadRemoteFiles();
+}
+
+QVariant ScriptsModel::data(const QModelIndex& index, int role) const {
+    const QList<ScriptItem*>* files = NULL;
+    int row = 0;
+    bool isLocal = index.row() < _localFiles.size();
+     if (isLocal) {
+         files = &_localFiles;
+         row = index.row();
+     } else {
+         files = &_remoteFiles;
+         row = index.row() - _localFiles.size();
+     }
+
+    if (role == Qt::DisplayRole) {
+        return QVariant((*files)[row]->getFilename() + (isLocal ? " (local)" : ""));
+    } else if (role == ScriptPath) {
+        return QVariant((*files)[row]->getFullPath());
+    }
+    return QVariant();
+}
+
+int ScriptsModel::rowCount(const QModelIndex& parent) const {
+    if (parent.isValid()) {
+        return 0;
+    }
+    return _localFiles.length() + _remoteFiles.length();
+}
+
+void ScriptsModel::updateScriptsLocation(const QString& newPath) {
+    _fsWatcher.removePath(_localDirectory.absolutePath());
+    _localDirectory.setPath(newPath);
+    _fsWatcher.addPath(_localDirectory.absolutePath());
+    reloadLocalFiles();
+}
+
+void ScriptsModel::reloadRemoteFiles() {
+    if (!_loadingScripts) {
+        _loadingScripts = true;
+        while (!_remoteFiles.isEmpty()) {
+            delete _remoteFiles.takeFirst();
+        }
+        requestRemoteFiles();
+    }
+}
+
+void ScriptsModel::requestRemoteFiles(QString marker) {
+    QUrl url(S3_URL);
+    QUrlQuery query;
+    query.addQueryItem(PREFIX_PARAMETER_NAME, MODELS_LOCATION);
+    if (!marker.isEmpty()) {
+        query.addQueryItem(MARKER_PARAMETER_NAME, marker);
+    }
+    url.setQuery(query);
+
+    QNetworkAccessManager* accessManager = new QNetworkAccessManager(this);
+    connect(accessManager, SIGNAL(finished(QNetworkReply*)), SLOT(downloadFinished(QNetworkReply*)));
+
+    QNetworkRequest request(url);
+    accessManager->get(request);
+}
+
+void ScriptsModel::downloadFinished(QNetworkReply* reply) {
+    bool finished = true;
+
+    if (reply->error() == QNetworkReply::NoError) {
+        QByteArray data = reply->readAll();
+
+        if (!data.isEmpty()) {
+            finished = parseXML(data);
+        } else {
+            qDebug() << "Error: Received no data when loading remote scripts";
+        }
+    }
+
+    reply->deleteLater();
+    sender()->deleteLater();
+
+    if (finished) {
+        _loadingScripts = false;
+    }
+}
+
+bool ScriptsModel::parseXML(QByteArray xmlFile) {
+    beginResetModel();
+
+    QXmlStreamReader xml(xmlFile);
+    QRegExp jsRegex(".*\\.js");
+    bool truncated = false;
+    QString lastKey;
+
+    while (!xml.atEnd() && !xml.hasError()) {
+        if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == IS_TRUNCATED_NAME) {
+            while (!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == IS_TRUNCATED_NAME)) {
+                xml.readNext();
+                if (xml.text().toString() == "true") {
+                    truncated = true;
+                }
+            }
+        }
+
+        if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == CONTAINER_NAME) {
+            while (!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == CONTAINER_NAME)) {
+                if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == KEY_NAME) {
+                    xml.readNext();
+                    lastKey = xml.text().toString();
+                    if (jsRegex.exactMatch(xml.text().toString())) {
+                        _remoteFiles.append(new ScriptItem(lastKey.mid(MODELS_LOCATION.length()), S3_URL + "/" + lastKey));
+                    }
+                }
+                xml.readNext();
+            }
+        }
+        xml.readNext();
+    }
+
+    endResetModel();
+
+    // Error handling
+    if (xml.hasError()) {
+        qDebug() << "Error loading remote scripts: " << xml.errorString();
+        return true;
+    }
+
+    if (truncated) {
+        requestRemoteFiles(lastKey);
+    }
+
+    // If this request was not truncated, we are done.
+    return !truncated;
+}
+
+void ScriptsModel::reloadLocalFiles() {
+    beginResetModel();
+
+    while (!_localFiles.isEmpty()) {
+        delete _localFiles.takeFirst();
+    }
+
+    _localDirectory.refresh();
+
+    const QFileInfoList localFiles = _localDirectory.entryInfoList();
+    for (int i = 0; i < localFiles.size(); i++) {
+        QFileInfo file = localFiles[i];
+        _localFiles.append(new ScriptItem(file.fileName(), file.absoluteFilePath()));
+    }
+
+    endResetModel();
+}
diff --git a/interface/src/ScriptsModel.h b/interface/src/ScriptsModel.h
new file mode 100644
index 0000000000..250c7eb9a8
--- /dev/null
+++ b/interface/src/ScriptsModel.h
@@ -0,0 +1,62 @@
+//
+//  ScriptsModel.h
+//  interface/src
+//
+//  Created by Ryan Huffman on 05/12/14.
+//  Copyright 2014 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
+//
+
+#ifndef hifi_ScriptsModel_h
+#define hifi_ScriptsModel_h
+
+#include <QAbstractListModel>
+#include <QDir>
+#include <QNetworkReply>
+#include <QFileSystemWatcher>
+
+class ScriptItem {
+public:
+    ScriptItem(const QString& filename, const QString& fullPath);
+
+    const QString& getFilename() { return _filename; };
+    const QString& getFullPath() { return _fullPath; };
+
+private:
+    QString _filename;
+    QString _fullPath;
+};
+
+class ScriptsModel : public QAbstractListModel {
+    Q_OBJECT
+public:
+    ScriptsModel(QObject* parent = NULL);
+
+    virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
+    virtual int rowCount(const QModelIndex& parent = QModelIndex()) const;
+
+    enum Role {
+        ScriptPath = Qt::UserRole,
+    };
+
+protected slots:
+    void updateScriptsLocation(const QString& newPath);
+    void downloadFinished(QNetworkReply* reply);
+    void reloadLocalFiles();
+    void reloadRemoteFiles();
+
+protected:
+    void requestRemoteFiles(QString marker = QString());
+    bool parseXML(QByteArray xmlFile);
+
+private:
+    bool _loadingScripts;
+    QDir _localDirectory;
+    QFileSystemWatcher _fsWatcher;
+    QList<ScriptItem*> _localFiles;
+    QList<ScriptItem*> _remoteFiles;
+};
+
+#endif // hifi_ScriptsModel_h
diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp
index 5ce1435bd6..4dbb015459 100644
--- a/interface/src/Util.cpp
+++ b/interface/src/Util.cpp
@@ -68,6 +68,7 @@ void printVector(glm::vec3 vec) {
     qDebug("%4.2f, %4.2f, %4.2f", vec.x, vec.y, vec.z);
 }
 
+
 //  Return the azimuth angle (in radians) between two points.
 float azimuth_to(glm::vec3 head_pos, glm::vec3 source_pos) {
     return atan2(head_pos.x - source_pos.x, head_pos.z - source_pos.z);
diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp
index baf46605fd..9b136980f4 100644
--- a/interface/src/avatar/Avatar.cpp
+++ b/interface/src/avatar/Avatar.cpp
@@ -48,6 +48,10 @@ Avatar::Avatar() :
     _skeletonModel(this),
     _bodyYawDelta(0.0f),
     _velocity(0.0f, 0.0f, 0.0f),
+    _lastVelocity(0.0f, 0.0f, 0.0f),
+    _acceleration(0.0f, 0.0f, 0.0f),
+    _angularVelocity(0.0f, 0.0f, 0.0f),
+    _lastOrientation(),
     _leanScale(0.5f),
     _scale(1.0f),
     _worldUpDirection(DEFAULT_UP_DIRECTION),
@@ -76,6 +80,7 @@ void Avatar::init() {
     _skeletonModel.init();
     _initialized = true;
     _shouldRenderBillboard = (getLODDistance() >= BILLBOARD_LOD_DISTANCE);
+    initializeHair();
 }
 
 glm::vec3 Avatar::getChestPosition() const {
@@ -134,10 +139,15 @@ void Avatar::simulate(float deltaTime) {
         head->setPosition(headPosition);
         head->setScale(_scale);
         head->simulate(deltaTime, false, _shouldRenderBillboard);
+        
+        if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
+            simulateHair(deltaTime);
+        }
     }
     
     // update position by velocity, and subtract the change added earlier for gravity
     _position += _velocity * deltaTime;
+    updateAcceleration(deltaTime);
     
     // update animation for display name fade in/out
     if ( _displayNameTargetAlpha != _displayNameAlpha) {
@@ -157,6 +167,17 @@ void Avatar::simulate(float deltaTime) {
     }
 }
 
+void Avatar::updateAcceleration(float deltaTime) {
+    // Linear Component of Acceleration
+    _acceleration = (_velocity - _lastVelocity) * (1.f / deltaTime);
+    _lastVelocity = _velocity;
+    //  Angular Component of Acceleration
+    glm::quat orientation = getOrientation();
+    glm::quat delta = glm::inverse(_lastOrientation) * orientation;
+    _angularVelocity = safeEulerAngles(delta) * (1.f / deltaTime);
+    _lastOrientation = getOrientation();
+}
+
 void Avatar::setMouseRay(const glm::vec3 &origin, const glm::vec3 &direction) {
     _mouseRayOrigin = origin;
     _mouseRayDirection = direction;
@@ -218,9 +239,6 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
             bool renderSkeleton = Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes);
             bool renderHead = Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes);
             bool renderBounding = Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes);
-            if (renderSkeleton || renderHead || renderBounding) {
-                updateShapePositions();
-            }
 
             if (renderSkeleton) {
                 _skeletonModel.renderJointCollisionShapes(0.7f);
@@ -230,7 +248,6 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
                 getHead()->getFaceModel().renderJointCollisionShapes(0.7f);
             }
             if (renderBounding && shouldRenderHead(cameraPosition, renderMode)) {
-                getHead()->getFaceModel().renderBoundingCollisionShapes(0.7f);
                 _skeletonModel.renderBoundingCollisionShapes(0.7f);
             }
 
@@ -361,6 +378,232 @@ void Avatar::renderBody(RenderMode renderMode, float glowLevel) {
         getHand()->render(false, modelRenderMode);
     }
     getHead()->render(1.0f, modelRenderMode);
+    if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
+        renderHair();
+    }
+}
+
+//
+//  Constants for the Hair Simulation
+//
+
+const float HAIR_LENGTH = 0.2f;
+const float HAIR_LINK_LENGTH = HAIR_LENGTH / HAIR_LINKS;
+const float HAIR_DAMPING = 0.99f;
+const float HEAD_RADIUS = 0.21f;
+const float CONSTRAINT_RELAXATION = 10.0f;
+const glm::vec3 HAIR_GRAVITY(0.0f, -0.007f, 0.0f);
+const float HAIR_ACCELERATION_COUPLING = 0.025f;
+const float HAIR_ANGULAR_VELOCITY_COUPLING = 0.10f;
+const float HAIR_MAX_LINEAR_ACCELERATION = 4.0f;
+const float HAIR_THICKNESS = 0.015f;
+const float HAIR_STIFFNESS = 0.0000f;
+const glm::vec3 HAIR_COLOR1(0.98f, 0.92f, 0.843f);
+const glm::vec3 HAIR_COLOR2(0.545f, 0.533f, 0.47f);
+const glm::vec3 WIND_DIRECTION(0.5f, -1.0f, 0.0f);
+const float MAX_WIND_STRENGTH = 0.02f;
+const float FINGER_LENGTH = 0.25f;
+const float FINGER_RADIUS = 0.10f;
+
+void Avatar::renderHair() {
+    //
+    //  Render the avatar's moveable hair
+    //
+    
+    glm::vec3 headPosition = getHead()->getPosition();
+    glPushMatrix();
+    glTranslatef(headPosition.x, headPosition.y, headPosition.z);
+    const glm::quat& rotation = getHead()->getFinalOrientationInWorldFrame();
+    glm::vec3 axis = glm::axis(rotation);
+    glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
+
+    glBegin(GL_QUADS);
+    for (int strand = 0; strand < HAIR_STRANDS; strand++) {
+        for (int link = 0; link < HAIR_LINKS - 1; link++) {
+            int vertexIndex = strand * HAIR_LINKS + link;
+            glColor3fv(&_hairColors[vertexIndex].x);
+            glNormal3fv(&_hairNormals[vertexIndex].x);
+            glVertex3f(_hairPosition[vertexIndex].x - _hairQuadDelta[vertexIndex].x,
+                       _hairPosition[vertexIndex].y - _hairQuadDelta[vertexIndex].y,
+                       _hairPosition[vertexIndex].z - _hairQuadDelta[vertexIndex].z);
+            glVertex3f(_hairPosition[vertexIndex].x + _hairQuadDelta[vertexIndex].x,
+                       _hairPosition[vertexIndex].y + _hairQuadDelta[vertexIndex].y,
+                       _hairPosition[vertexIndex].z + _hairQuadDelta[vertexIndex].z);
+            
+            glVertex3f(_hairPosition[vertexIndex + 1].x + _hairQuadDelta[vertexIndex].x,
+                       _hairPosition[vertexIndex + 1].y + _hairQuadDelta[vertexIndex].y,
+                       _hairPosition[vertexIndex + 1].z + _hairQuadDelta[vertexIndex].z);
+            glVertex3f(_hairPosition[vertexIndex + 1].x - _hairQuadDelta[vertexIndex].x,
+                       _hairPosition[vertexIndex + 1].y - _hairQuadDelta[vertexIndex].y,
+                       _hairPosition[vertexIndex + 1].z - _hairQuadDelta[vertexIndex].z);
+        }
+    }
+    glEnd();
+    
+    glPopMatrix();
+
+}
+
+void Avatar::simulateHair(float deltaTime) {
+
+    deltaTime = glm::clamp(deltaTime, 0.0f, 1.0f / 30.0f);
+    glm::vec3 acceleration = getAcceleration();
+    if (glm::length(acceleration) > HAIR_MAX_LINEAR_ACCELERATION) {
+        acceleration = glm::normalize(acceleration) * HAIR_MAX_LINEAR_ACCELERATION;
+    }
+    const glm::quat& rotation = getHead()->getFinalOrientationInWorldFrame();
+    acceleration = acceleration * rotation;
+    glm::vec3 angularVelocity = getAngularVelocity() + getHead()->getAngularVelocity();
+    
+    //  Get hand positions to allow touching hair
+    glm::vec3 leftHandPosition, rightHandPosition;
+    getSkeletonModel().getLeftHandPosition(leftHandPosition);
+    getSkeletonModel().getRightHandPosition(rightHandPosition);
+    leftHandPosition -= getHead()->getPosition();
+    rightHandPosition -= getHead()->getPosition();
+    glm::quat leftRotation, rightRotation;
+    getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getLeftHandJointIndex(), leftRotation);
+    getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getRightHandJointIndex(), rightRotation);
+    leftHandPosition += glm::vec3(0.0f, FINGER_LENGTH, 0.0f) * glm::inverse(leftRotation);
+    rightHandPosition += glm::vec3(0.0f, FINGER_LENGTH, 0.0f) * glm::inverse(rightRotation);
+    leftHandPosition = leftHandPosition * rotation;
+    rightHandPosition = rightHandPosition * rotation;
+    
+    float windIntensity = randFloat() * MAX_WIND_STRENGTH;
+    
+    for (int strand = 0; strand < HAIR_STRANDS; strand++) {
+        for (int link = 0; link < HAIR_LINKS; link++) {
+            int vertexIndex = strand * HAIR_LINKS + link;
+            if (vertexIndex % HAIR_LINKS == 0) {
+                //  Base Joint - no integration
+             } else {
+                //
+                //  Vertlet Integration
+                //
+                //  Add velocity from last position, with damping
+                glm::vec3 thisPosition = _hairPosition[vertexIndex];
+                glm::vec3 diff = thisPosition - _hairLastPosition[vertexIndex];
+                _hairPosition[vertexIndex] += diff * HAIR_DAMPING;
+                //  Resolve collision with head sphere
+                if (glm::length(_hairPosition[vertexIndex]) < HEAD_RADIUS) {
+                    _hairPosition[vertexIndex] += glm::normalize(_hairPosition[vertexIndex]) *
+                    (HEAD_RADIUS - glm::length(_hairPosition[vertexIndex]));
+                }
+                //  Resolve collision with hands
+                if (glm::length(_hairPosition[vertexIndex] - leftHandPosition) < FINGER_RADIUS) {
+                     _hairPosition[vertexIndex] += glm::normalize(_hairPosition[vertexIndex] - leftHandPosition) *
+                     (FINGER_RADIUS - glm::length(_hairPosition[vertexIndex] - leftHandPosition));
+                }
+                 if (glm::length(_hairPosition[vertexIndex] - rightHandPosition) < FINGER_RADIUS) {
+                     _hairPosition[vertexIndex] += glm::normalize(_hairPosition[vertexIndex] - rightHandPosition) *
+                     (FINGER_RADIUS - glm::length(_hairPosition[vertexIndex] - rightHandPosition));
+                 }
+
+                 
+                //  Add a little gravity
+                _hairPosition[vertexIndex] += HAIR_GRAVITY * rotation * deltaTime;
+                
+                //  Add linear acceleration of the avatar body
+                _hairPosition[vertexIndex] -= acceleration * HAIR_ACCELERATION_COUPLING * deltaTime;
+                
+                //  Add stiffness (like hair care products do)
+                _hairPosition[vertexIndex] += (_hairOriginalPosition[vertexIndex] - _hairPosition[vertexIndex])
+                                                * powf(1.f - link / HAIR_LINKS, 2.f) * HAIR_STIFFNESS;
+                
+                //  Add some wind
+                glm::vec3 wind = WIND_DIRECTION * windIntensity;
+                _hairPosition[vertexIndex] += wind * deltaTime;
+                
+                const float ANGULAR_VELOCITY_MIN = 0.001f;
+                //  Add angular acceleration of the avatar body
+                if (glm::length(angularVelocity) > ANGULAR_VELOCITY_MIN) {
+                    glm::vec3 yawVector = _hairPosition[vertexIndex];
+                    yawVector.y = 0.f;
+                    if (glm::length(yawVector) > EPSILON) {
+                        float radius = glm::length(yawVector);
+                        yawVector = glm::normalize(yawVector);
+                        float angle = atan2f(yawVector.x, -yawVector.z) + PI;
+                        glm::vec3 delta = glm::vec3(-1.f, 0.f, 0.f) * glm::angleAxis(angle, glm::vec3(0, 1, 0));
+                        _hairPosition[vertexIndex] -= delta * radius * angularVelocity.y * HAIR_ANGULAR_VELOCITY_COUPLING * deltaTime;
+                    }
+                    glm::vec3 pitchVector = _hairPosition[vertexIndex];
+                    pitchVector.x = 0.f;
+                    if (glm::length(pitchVector) > EPSILON) {
+                        float radius = glm::length(pitchVector);
+                        pitchVector = glm::normalize(pitchVector);
+                        float angle = atan2f(pitchVector.y, -pitchVector.z) + PI;
+                        glm::vec3 delta = glm::vec3(0.0f, 1.0f, 0.f) * glm::angleAxis(angle, glm::vec3(1, 0, 0));
+                        _hairPosition[vertexIndex] -= delta * radius * angularVelocity.x * HAIR_ANGULAR_VELOCITY_COUPLING * deltaTime;
+                    }
+                    glm::vec3 rollVector = _hairPosition[vertexIndex];
+                    rollVector.z = 0.f;
+                    if (glm::length(rollVector) > EPSILON) {
+                        float radius = glm::length(rollVector);
+                        pitchVector = glm::normalize(rollVector);
+                        float angle = atan2f(rollVector.x, rollVector.y) + PI;
+                        glm::vec3 delta = glm::vec3(-1.0f, 0.0f, 0.f) * glm::angleAxis(angle, glm::vec3(0, 0, 1));
+                        _hairPosition[vertexIndex] -= delta * radius * angularVelocity.z * HAIR_ANGULAR_VELOCITY_COUPLING * deltaTime;
+                    }
+                }
+                
+                //  Iterate length constraints to other links
+                for (int link = 0; link < HAIR_MAX_CONSTRAINTS; link++) {
+                    if (_hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS + link] > -1) {
+                        //  If there is a constraint, try to enforce it
+                        glm::vec3 vectorBetween = _hairPosition[_hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS + link]] - _hairPosition[vertexIndex];
+                        _hairPosition[vertexIndex] += glm::normalize(vectorBetween) * (glm::length(vectorBetween) - HAIR_LINK_LENGTH) * CONSTRAINT_RELAXATION * deltaTime;
+                    }
+                }
+                //  Store start position for next vertlet pass
+                _hairLastPosition[vertexIndex] = thisPosition;
+            }
+        }
+    }
+}
+
+void Avatar::initializeHair() {
+    const float FACE_WIDTH =  PI / 4.0f;
+    glm::vec3 thisVertex;
+    for (int strand = 0; strand < HAIR_STRANDS; strand++) {
+        float strandAngle = randFloat() * PI;
+        float azimuth = FACE_WIDTH / 2.0f + (randFloat() * (2.0 * PI - FACE_WIDTH));
+        float elevation = PI_OVER_TWO - (randFloat() * 0.75 * PI);
+        glm::vec3 thisStrand(sinf(azimuth) * cosf(elevation), sinf(elevation), -cosf(azimuth) * cosf(elevation));
+        thisStrand *= HEAD_RADIUS;
+        
+        for (int link = 0; link < HAIR_LINKS; link++) {
+            int vertexIndex = strand * HAIR_LINKS + link;
+            //  Clear constraints
+            for (int link2 = 0; link2 < HAIR_MAX_CONSTRAINTS; link2++) {
+                _hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS + link2] = -1;
+            }
+            if (vertexIndex % HAIR_LINKS == 0) {
+                //  start of strand
+                thisVertex = thisStrand;
+            } else {
+                thisVertex+= glm::normalize(thisStrand) * HAIR_LINK_LENGTH;
+                //  Set constraints to vertex before and maybe vertex after in strand
+                _hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS] = vertexIndex - 1;
+                if (link < (HAIR_LINKS - 1)) {
+                    _hairConstraints[vertexIndex * HAIR_MAX_CONSTRAINTS + 1] = vertexIndex + 1;
+                }
+            }
+            _hairPosition[vertexIndex] = thisVertex;
+            _hairLastPosition[vertexIndex] = _hairPosition[vertexIndex];
+            _hairOriginalPosition[vertexIndex] = _hairPosition[vertexIndex];
+
+            _hairQuadDelta[vertexIndex] = glm::vec3(cos(strandAngle) * HAIR_THICKNESS, 0.f, sin(strandAngle) * HAIR_THICKNESS);
+            _hairQuadDelta[vertexIndex] *= 1.f - (link / HAIR_LINKS);
+            _hairNormals[vertexIndex] = glm::normalize(randVector());
+            if (randFloat() < elevation / PI_OVER_TWO) {
+                _hairColors[vertexIndex] = HAIR_COLOR1 * ((float)(link + 1) / (float)HAIR_LINKS);
+            } else {
+                _hairColors[vertexIndex] = HAIR_COLOR2 * ((float)(link + 1) / (float)HAIR_LINKS);
+            }
+            
+        }
+    }
+    qDebug() << "Initialize Hair";
 }
 
 bool Avatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const {
@@ -579,10 +822,9 @@ bool Avatar::findRayIntersection(const glm::vec3& origin, const glm::vec3& direc
     return false;
 }
 
-bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
-        CollisionList& collisions, int skeletonSkipIndex) {
-    return _skeletonModel.findSphereCollisions(penetratorCenter, penetratorRadius, collisions, skeletonSkipIndex);
-    // Temporarily disabling collisions against the head because most of its collision proxies are bad.
+bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius, CollisionList& collisions) {
+    return _skeletonModel.findSphereCollisions(penetratorCenter, penetratorRadius, collisions);
+    // TODO: Andrew to fix: Temporarily disabling collisions against the head
     //return getHead()->getFaceModel().findSphereCollisions(penetratorCenter, penetratorRadius, collisions);
 }
 
@@ -591,18 +833,6 @@ bool Avatar::findPlaneCollisions(const glm::vec4& plane, CollisionList& collisio
         getHead()->getFaceModel().findPlaneCollisions(plane, collisions);
 }
 
-void Avatar::updateShapePositions() {
-    _skeletonModel.updateShapePositions();
-    Model& headModel = getHead()->getFaceModel();
-    headModel.updateShapePositions();
-    /* KEEP FOR DEBUG: use this in rather than code above to see shapes 
-     * in their default positions where the bounding shape is computed.
-    _skeletonModel.resetShapePositions();
-    Model& headModel = getHead()->getFaceModel();
-    headModel.resetShapePositions();
-    */
-}
-
 bool Avatar::findCollisions(const QVector<const Shape*>& shapes, CollisionList& collisions) {
     // TODO: Andrew to fix: also collide against _skeleton
     //bool collided = _skeletonModel.findCollisions(shapes, collisions);
@@ -613,69 +843,6 @@ bool Avatar::findCollisions(const QVector<const Shape*>& shapes, CollisionList&
     return collided;
 }
 
-bool Avatar::findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions) {
-    if (_collisionGroups & COLLISION_GROUP_PARTICLES) {
-        return false;
-    }
-    bool collided = false;
-    // first do the hand collisions
-    const HandData* handData = getHandData();
-    if (handData) {
-        for (int i = 0; i < NUM_HANDS; i++) {
-            const PalmData* palm = handData->getPalm(i);
-            if (palm && palm->hasPaddle()) {
-                // create a disk collision proxy where the hand is
-                int jointIndex = -1;
-                glm::vec3 handPosition;
-                if (i == 0) {
-                    _skeletonModel.getLeftHandPosition(handPosition);
-                    jointIndex = _skeletonModel.getLeftHandJointIndex();
-                }
-                else {
-                    _skeletonModel.getRightHandPosition(handPosition);
-                    jointIndex = _skeletonModel.getRightHandJointIndex();
-                }
-
-                glm::vec3 fingerAxis = palm->getFingerDirection();
-                glm::vec3 diskCenter = handPosition + HAND_PADDLE_OFFSET * fingerAxis;
-                glm::vec3 diskNormal = palm->getNormal();
-                const float DISK_THICKNESS = 0.08f;
-
-                // collide against the disk
-                glm::vec3 penetration;
-                if (findSphereDiskPenetration(particleCenter, particleRadius, 
-                            diskCenter, HAND_PADDLE_RADIUS, DISK_THICKNESS, diskNormal,
-                            penetration)) {
-                    CollisionInfo* collision = collisions.getNewCollision();
-                    if (collision) {
-                        collision->_type = COLLISION_TYPE_PADDLE_HAND;
-                        collision->_intData = jointIndex;
-                        collision->_penetration = penetration;
-                        collision->_addedVelocity = palm->getVelocity();
-                        collided = true;
-                    } else {
-                        // collisions are full, so we might as well bail now
-                        return collided;
-                    }
-                }
-            }
-        }
-    }
-    // then collide against the models
-    int preNumCollisions = collisions.size();
-    if (_skeletonModel.findSphereCollisions(particleCenter, particleRadius, collisions)) {
-        // the Model doesn't have velocity info, so we have to set it for each new collision
-        int postNumCollisions = collisions.size();
-        for (int i = preNumCollisions; i < postNumCollisions; ++i) {
-            CollisionInfo* collision = collisions.getCollision(i);
-            collision->_penetration /= (float)(TREE_SCALE);
-            collision->_addedVelocity = getVelocity();
-        }
-        collided = true;
-    }
-    return collided;
-}
-
 glm::quat Avatar::getJointRotation(int index) const {
     if (QThread::currentThread() != thread()) {
         return AvatarData::getJointRotation(index);
@@ -909,25 +1076,6 @@ float Avatar::getHeadHeight() const {
     return DEFAULT_HEAD_HEIGHT;
 }
 
-bool Avatar::collisionWouldMoveAvatar(CollisionInfo& collision) const {
-    if (!collision._data || collision._type != COLLISION_TYPE_MODEL) {
-        return false;
-    }
-    Model* model = static_cast<Model*>(collision._data);
-    int jointIndex = collision._intData;
-
-    if (model == &(_skeletonModel) && jointIndex != -1) {
-        // collision response of skeleton is temporarily disabled
-        return false;
-        //return _skeletonModel.collisionHitsMoveableJoint(collision);
-    }
-    if (model == &(getHead()->getFaceModel())) {
-        // ATM we always handle COLLISION_TYPE_MODEL against the face.
-        return true;
-    }
-    return false;
-}
-
 float Avatar::getBoundingRadius() const {
     // TODO: also use head model when computing the avatar's bounding radius
     return _skeletonModel.getBoundingRadius();
diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h
index f928881068..f20db1019d 100755
--- a/interface/src/avatar/Avatar.h
+++ b/interface/src/avatar/Avatar.h
@@ -32,6 +32,10 @@ static const float RESCALING_TOLERANCE = .02f;
 extern const float CHAT_MESSAGE_SCALE;
 extern const float CHAT_MESSAGE_HEIGHT;
 
+const int HAIR_STRANDS = 150;           //  Number of strands of hair
+const int HAIR_LINKS = 10;              //  Number of links in a hair strand
+const int HAIR_MAX_CONSTRAINTS = 2;     //  Hair verlet is connected to at most how many others
+
 enum DriveKeys {
     FWD = 0,
     BACK,
@@ -101,14 +105,12 @@ public:
     /// \return true if at least one shape collided with avatar
     bool findCollisions(const QVector<const Shape*>& shapes, CollisionList& collisions);
 
-    /// Checks for penetration between the described sphere and the avatar.
+    /// Checks for penetration between the a sphere and the avatar's models.
     /// \param penetratorCenter the center of the penetration test sphere
     /// \param penetratorRadius the radius of the penetration test sphere
     /// \param collisions[out] a list to which collisions get appended
-    /// \param skeletonSkipIndex if not -1, the index of a joint to skip (along with its descendents) in the skeleton model
     /// \return whether or not the sphere penetrated
-    bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
-        CollisionList& collisions, int skeletonSkipIndex = -1);
+    bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius, CollisionList& collisions);
 
     /// Checks for penetration between the described plane and the avatar.
     /// \param plane the penetration plane
@@ -116,13 +118,6 @@ public:
     /// \return whether or not the plane penetrated
     bool findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions);
 
-    /// Checks for collision between the a spherical particle and the avatar (including paddle hands)
-    /// \param collisionCenter the center of particle's bounding sphere
-    /// \param collisionRadius the radius of particle's bounding sphere
-    /// \param collisions[out] a list to which collisions get appended
-    /// \return whether or not the particle collided
-    bool findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions);
-
     virtual bool isMyAvatar() { return false; }
     
     virtual glm::quat getJointRotation(int index) const;
@@ -141,14 +136,10 @@ public:
 
     static void renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2);
 
-    /// \return true if we expect the avatar would move as a result of the collision
-    bool collisionWouldMoveAvatar(CollisionInfo& collision) const;
-
     virtual void applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration) { }
 
     /// \return bounding radius of avatar
     virtual float getBoundingRadius() const;
-    void updateShapePositions();
 
     quint32 getCollisionGroups() const { return _collisionGroups; }
     virtual void setCollisionGroups(quint32 collisionGroups) { _collisionGroups = (collisionGroups & VALID_COLLISION_GROUPS); }
@@ -158,6 +149,9 @@ public:
     Q_INVOKABLE glm::quat getJointCombinedRotation(int index) const;
     Q_INVOKABLE glm::quat getJointCombinedRotation(const QString& name) const;
     
+    glm::vec3 getAcceleration() const { return _acceleration; }
+    glm::vec3 getAngularVelocity() const { return _angularVelocity; }
+    
 public slots:
     void updateCollisionGroups();
 
@@ -169,6 +163,10 @@ protected:
     QVector<Model*> _attachmentModels;
     float _bodyYawDelta;
     glm::vec3 _velocity;
+    glm::vec3 _lastVelocity;
+    glm::vec3 _acceleration;
+    glm::vec3 _angularVelocity;
+    glm::quat _lastOrientation;
     float _leanScale;
     float _scale;
     glm::vec3 _worldUpDirection;
@@ -185,6 +183,7 @@ protected:
     glm::vec3 getBodyFrontDirection() const { return getOrientation() * IDENTITY_FRONT; }
     glm::quat computeRotationFromBodyToWorldUp(float proportion = 1.0f) const;
     void setScale(float scale);
+    void updateAcceleration(float deltaTime);
 
     float getSkeletonHeight() const;
     float getHeadHeight() const;
@@ -200,6 +199,18 @@ protected:
     virtual void renderAttachments(RenderMode renderMode);
 
     virtual void updateJointMappings();
+    
+    glm::vec3 _hairPosition[HAIR_STRANDS * HAIR_LINKS];
+    glm::vec3 _hairOriginalPosition[HAIR_STRANDS * HAIR_LINKS];
+    glm::vec3 _hairLastPosition[HAIR_STRANDS * HAIR_LINKS];
+    glm::vec3 _hairQuadDelta[HAIR_STRANDS * HAIR_LINKS];
+    glm::vec3 _hairNormals[HAIR_STRANDS * HAIR_LINKS];
+    glm::vec3 _hairColors[HAIR_STRANDS * HAIR_LINKS];
+    int _hairIsMoveable[HAIR_STRANDS * HAIR_LINKS];
+    int _hairConstraints[HAIR_STRANDS * HAIR_LINKS * 2];     // Hair can link to two others
+    void renderHair();
+    void simulateHair(float deltaTime);
+    void initializeHair();
 
 private:
 
@@ -211,6 +222,7 @@ private:
     void renderBillboard();
     
     float getBillboardSize() const;
+    
 };
 
 #endif // hifi_Avatar_h
diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp
index 3aff984893..94f734ba06 100644
--- a/interface/src/avatar/Hand.cpp
+++ b/interface/src/avatar/Hand.cpp
@@ -94,39 +94,6 @@ void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
     }
 }
 
-void Hand::collideAgainstOurself() {
-    if (!Menu::getInstance()->isOptionChecked(MenuOption::HandsCollideWithSelf)) {
-        return;
-    }
-
-    int leftPalmIndex, rightPalmIndex;   
-    getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex);
-    float scaledPalmRadius = PALM_COLLISION_RADIUS * _owningAvatar->getScale();
-    
-    const SkeletonModel& skeletonModel = _owningAvatar->getSkeletonModel();
-    for (int i = 0; i < int(getNumPalms()); i++) {
-        PalmData& palm = getPalms()[i];
-        if (!palm.isActive()) {
-            continue;
-        }
-        // ignoring everything below the parent of the parent of the last free joint
-        int skipIndex = skeletonModel.getParentJointIndex(skeletonModel.getParentJointIndex(
-            skeletonModel.getLastFreeJointIndex((int(i) == leftPalmIndex) ? skeletonModel.getLeftHandJointIndex() :
-                (int(i) == rightPalmIndex) ? skeletonModel.getRightHandJointIndex() : -1)));
-
-        handCollisions.clear();
-        if (_owningAvatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, handCollisions, skipIndex)) {
-            glm::vec3 totalPenetration;
-            for (int j = 0; j < handCollisions.size(); ++j) {
-                CollisionInfo* collision = handCollisions.getCollision(j);
-                totalPenetration = addPenetrations(totalPenetration, collision->_penetration);
-            }
-            // resolve penetration
-            palm.addToPenetration(totalPenetration);
-        }
-    }
-}
-
 void Hand::resolvePenetrations() {
     for (size_t i = 0; i < getNumPalms(); ++i) {
         PalmData& palm = getPalms()[i];
diff --git a/interface/src/avatar/Hand.h b/interface/src/avatar/Hand.h
index 5d171f2809..ed2fa3e1ab 100755
--- a/interface/src/avatar/Hand.h
+++ b/interface/src/avatar/Hand.h
@@ -54,7 +54,6 @@ public:
     void render(bool isMine, Model::RenderMode renderMode = Model::DEFAULT_RENDER_MODE);
 
     void collideAgainstAvatar(Avatar* avatar, bool isMyHand);
-    void collideAgainstOurself();
 
     void resolvePenetrations();
 
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index 3482c380a0..1e50ea9d87 100644
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -76,14 +76,21 @@ MyAvatar::MyAvatar() :
     _lastFloorContactPoint(0.0f),
     _lookAtTargetAvatar(),
     _shouldRender(true),
-    _billboardValid(false)
+    _billboardValid(false),
+    _physicsSimulation()
 {
     for (int i = 0; i < MAX_DRIVE_KEYS; i++) {
         _driveKeys[i] = 0.0f;
     }
+    _skeletonModel.setEnableShapes(true);
+    // The skeleton is both a PhysicsEntity and Ragdoll, so we add it to the simulation once for each type.
+    _physicsSimulation.addEntity(&_skeletonModel);
+    _physicsSimulation.addRagdoll(&_skeletonModel);
 }
 
 MyAvatar::~MyAvatar() {
+    _physicsSimulation.removeEntity(&_skeletonModel);
+    _physicsSimulation.removeRagdoll(&_skeletonModel);
     _lookAtTargetAvatar.clear();
 }
 
@@ -154,7 +161,6 @@ void MyAvatar::simulate(float deltaTime) {
     {
         PerformanceTimer perfTimer("MyAvatar::simulate/hand Collision,simulate");
         // update avatar skeleton and simulate hand and head
-        getHand()->collideAgainstOurself(); 
         getHand()->simulate(deltaTime, true);
     }
 
@@ -188,6 +194,25 @@ void MyAvatar::simulate(float deltaTime) {
         head->setScale(_scale);
         head->simulate(deltaTime, true);
     }
+    
+    {
+        PerformanceTimer perfTimer("MyAvatar::simulate/hair Simulate");
+        if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
+            simulateHair(deltaTime);
+        }
+    }
+
+    {
+        PerformanceTimer perfTimer("MyAvatar::simulate/ragdoll");
+        if (Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagdoll)) {
+            const int minError = 0.01f;
+            const float maxIterations = 10;
+            const quint64 maxUsec = 2000;
+            _physicsSimulation.stepForward(deltaTime, minError, maxIterations, maxUsec);
+        } else {
+            _skeletonModel.moveShapesTowardJoints(1.0f);
+        }
+    }
 
     // now that we're done stepping the avatar forward in time, compute new collisions
     if (_collisionGroups != 0) {
@@ -199,7 +224,6 @@ void MyAvatar::simulate(float deltaTime) {
             radius = myCamera->getAspectRatio() * (myCamera->getNearClip() / cos(myCamera->getFieldOfView() / 2.0f));
             radius *= COLLISION_RADIUS_SCALAR;
         }
-        updateShapePositions();
         if (_collisionGroups & COLLISION_GROUP_ENVIRONMENT) {
             PerformanceTimer perfTimer("MyAvatar::simulate/updateCollisionWithEnvironment");
             updateCollisionWithEnvironment(deltaTime, radius);
@@ -210,10 +234,12 @@ void MyAvatar::simulate(float deltaTime) {
         } else {
             _trapDuration = 0.0f;
         }
+    /* TODO: Andrew to make this work
         if (_collisionGroups & COLLISION_GROUP_AVATARS) {
             PerformanceTimer perfTimer("MyAvatar::simulate/updateCollisionWithAvatars");
             updateCollisionWithAvatars(deltaTime);
         }
+    */
     }
 
     // consider updating our billboard
@@ -378,6 +404,7 @@ void MyAvatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
     if (!_shouldRender) {
         return; // exit early
     }
+
     Avatar::render(cameraPosition, renderMode);
     
     // don't display IK constraints in shadow mode
@@ -430,6 +457,25 @@ void MyAvatar::renderHeadMouse(int screenWidth, int screenHeight) const {
     }
 }
 
+const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f);
+
+glm::vec3 MyAvatar::getLeftPalmPosition() {
+    glm::vec3 leftHandPosition;
+    getSkeletonModel().getLeftHandPosition(leftHandPosition);
+    glm::quat leftRotation;
+    getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getLeftHandJointIndex(), leftRotation);
+    leftHandPosition += HAND_TO_PALM_OFFSET * glm::inverse(leftRotation);
+    return leftHandPosition;
+}
+glm::vec3 MyAvatar::getRightPalmPosition() {
+    glm::vec3 rightHandPosition;
+    getSkeletonModel().getRightHandPosition(rightHandPosition);
+    glm::quat rightRotation;
+    getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getRightHandJointIndex(), rightRotation);
+    rightHandPosition += HAND_TO_PALM_OFFSET * glm::inverse(rightRotation);
+    return rightHandPosition;
+}
+
 void MyAvatar::setLocalGravity(glm::vec3 gravity) {
     _motionBehaviors |= AVATAR_MOTION_OBEY_LOCAL_GRAVITY;
     // Environmental and Local gravities are incompatible.  Since Local is being set here
@@ -839,8 +885,13 @@ void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) {
     renderAttachments(renderMode);
     
     //  Render head so long as the camera isn't inside it
-    if (shouldRenderHead(Application::getInstance()->getCamera()->getPosition(), renderMode)) {
+    const Camera *camera = Application::getInstance()->getCamera();
+    const glm::vec3 cameraPos = camera->getPosition() + (camera->getRotation() * glm::vec3(0.0f, 0.0f, 1.0f)) * camera->getDistance();
+    if (shouldRenderHead(cameraPos, renderMode)) {
         getHead()->render(1.0f, modelRenderMode);
+        if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) {
+            renderHair();
+        }
     }
     getHand()->render(true, modelRenderMode);
 }
@@ -891,11 +942,26 @@ void MyAvatar::updateOrientation(float deltaTime) {
         float yaw, pitch, roll; 
         OculusManager::getEulerAngles(yaw, pitch, roll);
         // ... so they need to be converted to degrees before we do math...
-
+        yaw *= DEGREES_PER_RADIAN;
+        pitch *= DEGREES_PER_RADIAN;
+        roll *= DEGREES_PER_RADIAN;
+        
+        // Record the angular velocity
         Head* head = getHead();
-        head->setBaseYaw(yaw * DEGREES_PER_RADIAN);
-        head->setBasePitch(pitch * DEGREES_PER_RADIAN);
-        head->setBaseRoll(roll * DEGREES_PER_RADIAN);
+        glm::vec3 angularVelocity(yaw - head->getBaseYaw(), pitch - head->getBasePitch(), roll - head->getBaseRoll());
+        head->setAngularVelocity(angularVelocity);
+        
+        //Invert yaw and roll when in mirror mode
+        if (Application::getInstance()->getCamera()->getMode() == CAMERA_MODE_MIRROR) {
+            head->setBaseYaw(-yaw);
+            head->setBasePitch(pitch);
+            head->setBaseRoll(-roll);
+        } else {
+            head->setBaseYaw(yaw);
+            head->setBasePitch(pitch);
+            head->setBaseRoll(roll);
+        }
+        
     }
 
     // update the euler angles
@@ -984,6 +1050,7 @@ void MyAvatar::updatePosition(float deltaTime) {
         } else { 
             _position += _velocity * deltaTime;
         }
+        updateAcceleration(deltaTime);
     }
 
     // update moving flag based on speed
@@ -1262,7 +1329,7 @@ void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) {
         float capsuleHalfHeight = boundingShape.getHalfHeight();
         const float MAX_STEP_HEIGHT = capsuleRadius + capsuleHalfHeight;
         const float MIN_STEP_HEIGHT = 0.0f;
-        glm::vec3 footBase = boundingShape.getPosition() - (capsuleRadius + capsuleHalfHeight) * _worldUpDirection;
+        glm::vec3 footBase = boundingShape.getTranslation() - (capsuleRadius + capsuleHalfHeight) * _worldUpDirection;
         float highestStep = 0.0f;
         float lowestStep = MAX_STEP_HEIGHT;
         glm::vec3 floorPoint;
@@ -1279,7 +1346,7 @@ void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) {
             if (horizontalDepth > capsuleRadius || fabsf(verticalDepth) > MAX_STEP_HEIGHT) {
                 isTrapped = true;
                 if (_trapDuration > MAX_TRAP_PERIOD) {
-                    float distance = glm::dot(boundingShape.getPosition() - cubeCenter, _worldUpDirection);
+                    float distance = glm::dot(boundingShape.getTranslation() - cubeCenter, _worldUpDirection);
                     if (distance < 0.0f) {
                         distance = fabsf(distance) + 0.5f * cubeSide;
                     }
@@ -1464,7 +1531,6 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
             // don't collide with ourselves
             continue;
         }
-        avatar->updateShapePositions();
         float distance = glm::length(_position - avatar->getPosition());        
         if (_distanceToNearestAvatar > distance) {
             _distanceToNearestAvatar = distance;
@@ -1490,17 +1556,10 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) {
                 }
             }
 
-            // collide our hands against them
-            // TODO: make this work when we can figure out when the other avatar won't yeild
-            // (for example, we're colliding against their chest or leg)
-            //getHand()->collideAgainstAvatar(avatar, true);
-
             // collide their hands against us
             avatar->getHand()->collideAgainstAvatar(this, false);
         }
     }
-    // TODO: uncomment this when we handle collisions that won't affect other avatar
-    //getHand()->resolvePenetrations();
 }
 
 class SortedAvatar {
diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h
index 2fbc488feb..0ee76c6b45 100644
--- a/interface/src/avatar/MyAvatar.h
+++ b/interface/src/avatar/MyAvatar.h
@@ -14,6 +14,8 @@
 
 #include <QSettings>
 
+#include <PhysicsSimulation.h>
+
 #include "Avatar.h"
 
 enum AvatarHandState
@@ -137,7 +139,10 @@ public slots:
     void setThrust(glm::vec3 newThrust) { _thrust = newThrust; }
 
     void updateMotionBehaviorsFromMenu();
-
+    
+    glm::vec3 getLeftPalmPosition();
+    glm::vec3 getRightPalmPosition();
+    
 signals:
     void transformChanged();
 
@@ -173,6 +178,7 @@ private:
     float _oculusYawOffset;
 
     QList<AnimationHandlePointer> _animationHandles;
+    PhysicsSimulation _physicsSimulation;
 
 	// private methods
     float computeDistanceToFloor(const glm::vec3& startPoint);
diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h
index 3b8e67df47..b91c112b6a 100644
--- a/interface/src/avatar/SkeletonModel.h
+++ b/interface/src/avatar/SkeletonModel.h
@@ -13,23 +13,23 @@
 #define hifi_SkeletonModel_h
 
 #include "renderer/Model.h"
-#include "renderer/RagDoll.h"
+
+#include <CapsuleShape.h>
+#include <Ragdoll.h>
 
 class Avatar;
 
 /// A skeleton loaded from a model.
-class SkeletonModel : public Model {
+class SkeletonModel : public Model, public Ragdoll {
     Q_OBJECT
     
 public:
 
-    SkeletonModel(Avatar* owningAvatar);
+    SkeletonModel(Avatar* owningAvatar, QObject* parent = NULL);
    
     void setJointStates(QVector<JointState> states);
 
     void simulate(float deltaTime, bool fullUpdate = true);
-    void simulateRagDoll(float deltaTime);
-    void updateShapePositions();
 
     /// \param jointIndex index of hand joint
     /// \param shapes[out] list in which is stored pointers to hand shapes
@@ -94,9 +94,27 @@ public:
     /// \return whether or not both eye meshes were found
     bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;
     
-    void renderRagDoll();
+    // virtual overrride from Ragdoll
+    virtual void stepRagdollForward(float deltaTime);
+
+    void moveShapesTowardJoints(float fraction);
+
+    void computeBoundingShape(const FBXGeometry& geometry);
+    void renderBoundingCollisionShapes(float alpha);
+    float getBoundingShapeRadius() const { return _boundingShape.getRadius(); }
+    const CapsuleShape& getBoundingShape() const { return _boundingShape; }
+
+    void resetShapePositions(); // DEBUG method
+
+    void renderRagdoll();
 protected:
 
+    // virtual overrrides from Ragdoll
+    void initRagdollPoints();
+    void buildRagdollConstraints();
+
+    void buildShapes();
+
     /// \param jointIndex index of joint in model
     /// \param position position of joint in model-frame
     void applyHandPosition(int jointIndex, const glm::vec3& position);
@@ -120,7 +138,9 @@ private:
     void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation);
     
     Avatar* _owningAvatar;
-    RagDoll _ragDoll;
+
+    CapsuleShape _boundingShape;
+    glm::vec3 _boundingShapeLocalOffset;
 };
 
 #endif // hifi_SkeletonModel_h
diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp
index b2ee4e8c18..dbe444bf9b 100644
--- a/interface/src/devices/OculusManager.cpp
+++ b/interface/src/devices/OculusManager.cpp
@@ -3,6 +3,7 @@
 //  interface/src/devices
 //
 //  Created by Stephen Birarda on 5/9/13.
+//  Refactored by Ben Arnold on 6/30/2014
 //  Copyright 2012 High Fidelity, Inc.
 //
 //  Distributed under the Apache License, Version 2.0.
@@ -11,67 +12,211 @@
 
 #include "InterfaceConfig.h"
 
+#include "OculusManager.h"
+
 #include <QOpenGLFramebufferObject>
 
 #include <glm/glm.hpp>
+#include <UserActivityLogger.h>
 
 #include "Application.h"
-#include "OculusManager.h"
+
+#ifdef HAVE_LIBOVR
+
+using namespace OVR;
 
 ProgramObject OculusManager::_program;
 int OculusManager::_textureLocation;
-int OculusManager::_lensCenterLocation;
-int OculusManager::_screenCenterLocation;
-int OculusManager::_scaleLocation;
-int OculusManager::_scaleInLocation;
-int OculusManager::_hmdWarpParamLocation;
+int OculusManager::_eyeToSourceUVScaleLocation;
+int OculusManager::_eyeToSourceUVOffsetLocation;
+int OculusManager::_eyeRotationStartLocation;
+int OculusManager::_eyeRotationEndLocation;
+int OculusManager::_positionAttributeLocation;
+int OculusManager::_colorAttributeLocation;
+int OculusManager::_texCoord0AttributeLocation;
+int OculusManager::_texCoord1AttributeLocation;
+int OculusManager::_texCoord2AttributeLocation;
 bool OculusManager::_isConnected = false;
 
-#ifdef HAVE_LIBOVR
-using namespace OVR;
-using namespace OVR::Util::Render;
+ovrHmd OculusManager::_ovrHmd;
+ovrHmdDesc OculusManager::_ovrHmdDesc;
+ovrFovPort OculusManager::_eyeFov[ovrEye_Count];
+ovrEyeRenderDesc OculusManager::_eyeRenderDesc[ovrEye_Count];
+ovrSizei OculusManager::_renderTargetSize;
+ovrVector2f OculusManager::_UVScaleOffset[ovrEye_Count][2];
+GLuint  OculusManager::_vertices[ovrEye_Count] = { 0, 0 };
+GLuint OculusManager::_indices[ovrEye_Count] = { 0, 0 };
+GLsizei OculusManager::_meshSize[ovrEye_Count] = { 0, 0 };
+ovrFrameTiming OculusManager::_hmdFrameTiming;
+ovrRecti OculusManager::_eyeRenderViewport[ovrEye_Count];
+unsigned int OculusManager::_frameIndex = 0;
+bool OculusManager::_frameTimingActive = false;
+bool OculusManager::_programInitialized = false;
+Camera* OculusManager::_camera = NULL;
 
-Ptr<DeviceManager> OculusManager::_deviceManager;
-Ptr<HMDDevice> OculusManager::_hmdDevice;
-Ptr<SensorDevice> OculusManager::_sensorDevice;
-SensorFusion* OculusManager::_sensorFusion;
-StereoConfig OculusManager::_stereoConfig;
 #endif
 
 void OculusManager::connect() {
 #ifdef HAVE_LIBOVR
-    System::Init();
-    _deviceManager = *DeviceManager::Create();
-    _hmdDevice = *_deviceManager->EnumerateDevices<HMDDevice>().CreateDevice();
+    ovr_Initialize();
 
-    if (_hmdDevice) {
+    _ovrHmd = ovrHmd_Create(0);
+    if (_ovrHmd) {
+        if (!_isConnected) {
+            UserActivityLogger::getInstance().connectedDevice("hmd", "oculus");
+        }
         _isConnected = true;
-        
-        _sensorDevice = *_hmdDevice->GetSensor();
-        _sensorFusion = new SensorFusion;
-        _sensorFusion->AttachToSensor(_sensorDevice);
-        _sensorFusion->SetPredictionEnabled(true);
+      
+        ovrHmd_GetDesc(_ovrHmd, &_ovrHmdDesc);
+
+        _eyeFov[0] = _ovrHmdDesc.DefaultEyeFov[0];
+        _eyeFov[1] = _ovrHmdDesc.DefaultEyeFov[1];
+
+        //Get texture size
+        ovrSizei recommendedTex0Size = ovrHmd_GetFovTextureSize(_ovrHmd, ovrEye_Left,
+                                                                _eyeFov[0], 1.0f);
+        ovrSizei recommendedTex1Size = ovrHmd_GetFovTextureSize(_ovrHmd, ovrEye_Right,
+                                                                _eyeFov[1], 1.0f);
+        _renderTargetSize.w = recommendedTex0Size.w + recommendedTex1Size.w;
+        _renderTargetSize.h = recommendedTex0Size.h;
+        if (_renderTargetSize.h < recommendedTex1Size.h) {
+            _renderTargetSize.h = recommendedTex1Size.h;
+        }
+
+        _eyeRenderDesc[0] = ovrHmd_GetRenderDesc(_ovrHmd, ovrEye_Left, _eyeFov[0]);
+        _eyeRenderDesc[1] = ovrHmd_GetRenderDesc(_ovrHmd, ovrEye_Right, _eyeFov[1]);
+
+        ovrHmd_SetEnabledCaps(_ovrHmd, ovrHmdCap_LowPersistence | ovrHmdCap_LatencyTest);
+
+        ovrHmd_StartSensor(_ovrHmd, ovrSensorCap_Orientation | ovrSensorCap_YawCorrection |
+                           ovrSensorCap_Position,
+                           ovrSensorCap_Orientation);
+
+        if (!_camera) {
+            _camera = new Camera;
+        }
+
+        if (!_programInitialized) {
+            // Shader program
+            _programInitialized = true;
+            _program.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/oculus.vert");
+            _program.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + "shaders/oculus.frag");
+            _program.link();
+
+            // Uniforms
+            _textureLocation = _program.uniformLocation("texture");
+            _eyeToSourceUVScaleLocation = _program.uniformLocation("EyeToSourceUVScale");
+            _eyeToSourceUVOffsetLocation = _program.uniformLocation("EyeToSourceUVOffset");
+            _eyeRotationStartLocation = _program.uniformLocation("EyeRotationStart");
+            _eyeRotationEndLocation = _program.uniformLocation("EyeRotationEnd");
+
+            // Attributes
+            _positionAttributeLocation = _program.attributeLocation("position");
+            _colorAttributeLocation = _program.attributeLocation("color");
+            _texCoord0AttributeLocation = _program.attributeLocation("texCoord0");
+            _texCoord1AttributeLocation = _program.attributeLocation("texCoord1");
+            _texCoord2AttributeLocation = _program.attributeLocation("texCoord2");
+        }
+
+        //Generate the distortion VBOs
+        generateDistortionMesh();
 
-        HMDInfo info;
-        _hmdDevice->GetDeviceInfo(&info);
-        _stereoConfig.SetHMDInfo(info);
-        
-        _program.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + "shaders/oculus.frag");
-        _program.link();
-        
-        _textureLocation = _program.uniformLocation("texture");
-        _lensCenterLocation = _program.uniformLocation("lensCenter");
-        _screenCenterLocation = _program.uniformLocation("screenCenter");
-        _scaleLocation = _program.uniformLocation("scale");
-        _scaleInLocation = _program.uniformLocation("scaleIn");
-        _hmdWarpParamLocation = _program.uniformLocation("hmdWarpParam");        
     } else {
-        _deviceManager.Clear();
-        System::Destroy();
+        _isConnected = false;
+        ovrHmd_Destroy(_ovrHmd);
+        ovr_Shutdown();
     }
 #endif
 }
 
+//Disconnects and deallocates the OR
+void OculusManager::disconnect() {
+#ifdef HAVE_LIBOVR
+    if (_isConnected) {
+        _isConnected = false;
+        ovrHmd_Destroy(_ovrHmd);
+        ovr_Shutdown();
+
+        //Free the distortion mesh data
+        for (int i = 0; i < ovrEye_Count; i++) {
+            if (_vertices[i] != 0) {
+                glDeleteBuffers(1, &(_vertices[i]));
+                _vertices[i] = 0;
+            }
+            if (_indices[i] != 0) {
+                glDeleteBuffers(1, &(_indices[i]));
+                _indices[i] = 0;
+            }
+        }
+    }
+#endif
+}
+
+#ifdef HAVE_LIBOVR
+void OculusManager::generateDistortionMesh() {
+
+    //Check if we already have the distortion mesh
+    if (_vertices[0] != 0) {
+        printf("WARNING: Tried to generate Oculus distortion mesh twice without freeing the VBOs.");
+        return;
+    }
+
+    //Viewport for the render target for each eye
+    _eyeRenderViewport[0].Pos = Vector2i(0, 0);
+    _eyeRenderViewport[0].Size = Sizei(_renderTargetSize.w / 2, _renderTargetSize.h);
+    _eyeRenderViewport[1].Pos = Vector2i((_renderTargetSize.w + 1) / 2, 0);
+    _eyeRenderViewport[1].Size = _eyeRenderViewport[0].Size;
+
+    for (int eyeNum = 0; eyeNum < ovrEye_Count; eyeNum++) {
+        // Allocate and generate distortion mesh vertices
+        ovrDistortionMesh meshData;
+        ovrHmd_CreateDistortionMesh(_ovrHmd, _eyeRenderDesc[eyeNum].Eye, _eyeRenderDesc[eyeNum].Fov, _ovrHmdDesc.DistortionCaps, &meshData);
+        
+        ovrHmd_GetRenderScaleAndOffset(_eyeRenderDesc[eyeNum].Fov, _renderTargetSize, _eyeRenderViewport[eyeNum],
+                                       _UVScaleOffset[eyeNum]);
+
+        // Parse the vertex data and create a render ready vertex buffer
+        DistortionVertex* pVBVerts = (DistortionVertex*)OVR_ALLOC(sizeof(DistortionVertex) * meshData.VertexCount);
+        _meshSize[eyeNum] = meshData.IndexCount;
+
+        // Convert the oculus vertex data to the DistortionVertex format.
+        DistortionVertex* v = pVBVerts;
+        ovrDistortionVertex* ov = meshData.pVertexData;
+        for (unsigned int vertNum = 0; vertNum < meshData.VertexCount; vertNum++) {
+            v->pos.x = ov->Pos.x;
+            v->pos.y = ov->Pos.y;
+            v->texR.x = ov->TexR.x;
+            v->texR.y = ov->TexR.y;
+            v->texG.x = ov->TexG.x;
+            v->texG.y = ov->TexG.y;
+            v->texB.x = ov->TexB.x;
+            v->texB.y = ov->TexB.y;
+            v->color.r = v->color.g = v->color.b = (GLubyte)(ov->VignetteFactor * 255.99f);
+            v->color.a = (GLubyte)(ov->TimeWarpFactor * 255.99f);
+            v++; 
+            ov++;
+        }
+
+        //vertices
+        glGenBuffers(1, &(_vertices[eyeNum]));
+        glBindBuffer(GL_ARRAY_BUFFER, _vertices[eyeNum]);
+        glBufferData(GL_ARRAY_BUFFER, sizeof(DistortionVertex) * meshData.VertexCount, pVBVerts, GL_STATIC_DRAW);
+        glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+        //indices
+        glGenBuffers(1, &(_indices[eyeNum]));
+        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indices[eyeNum]);
+        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned short) * meshData.IndexCount, meshData.pIndexData, GL_STATIC_DRAW);
+        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+        
+        //Now that we have the VBOs we can get rid of the mesh data
+        OVR_FREE(pVBVerts);
+        ovrHmd_DestroyDistortionMesh(&meshData);
+    }
+
+}
+#endif
+
 bool OculusManager::isConnected() {
 #ifdef HAVE_LIBOVR
     return _isConnected && Menu::getInstance()->isOptionChecked(MenuOption::EnableVRMode);
@@ -80,137 +225,237 @@ bool OculusManager::isConnected() {
 #endif
 }
 
+//Begins the frame timing for oculus prediction purposes
+void OculusManager::beginFrameTiming() {
+#ifdef HAVE_LIBOVR
+
+    if (_frameTimingActive) {
+        printf("WARNING: Called OculusManager::beginFrameTiming() twice in a row, need to call OculusManager::endFrameTiming().");
+    }
+
+   _hmdFrameTiming = ovrHmd_BeginFrameTiming(_ovrHmd, _frameIndex);
+   _frameTimingActive = true;
+#endif
+}
+
+//Ends frame timing
+void OculusManager::endFrameTiming() {
+#ifdef HAVE_LIBOVR
+    ovrHmd_EndFrameTiming(_ovrHmd);
+    _frameIndex++;
+    _frameTimingActive = false;
+#endif
+}
+
+//Sets the camera FoV and aspect ratio
 void OculusManager::configureCamera(Camera& camera, int screenWidth, int screenHeight) {
 #ifdef HAVE_LIBOVR
-    _stereoConfig.SetFullViewport(Viewport(0, 0, screenWidth, screenHeight));
-    camera.setAspectRatio(_stereoConfig.GetAspect());
-    camera.setFieldOfView(_stereoConfig.GetYFOVDegrees());
+    camera.setAspectRatio(_renderTargetSize.w / _renderTargetSize.h);
+    camera.setFieldOfView(atan(_eyeFov[0].UpTan) * DEGREES_PER_RADIAN * 2.0f);
 #endif    
 }
 
-void OculusManager::display(Camera& whichCamera) {
+//Displays everything for the oculus, frame timing must be active
+void OculusManager::display(const glm::quat &bodyOrientation, const glm::vec3 &position, Camera& whichCamera) {
 #ifdef HAVE_LIBOVR
+    //beginFrameTiming must be called before display
+    if (!_frameTimingActive) {
+        printf("WARNING: Called OculusManager::display() without calling OculusManager::beginFrameTiming() first.");
+        return;
+    }
+
     ApplicationOverlay& applicationOverlay = Application::getInstance()->getApplicationOverlay();
+
     // We only need to render the overlays to a texture once, then we just render the texture as a quad
     // PrioVR will only work if renderOverlay is called, calibration is connected to Application::renderingOverlay() 
     applicationOverlay.renderOverlay(true);
     const bool displayOverlays = Menu::getInstance()->isOptionChecked(MenuOption::DisplayOculusOverlays);
-    
-    Application::getInstance()->getGlowEffect()->prepare(); 
 
-    // render the left eye view to the left side of the screen
-    const StereoEyeParams& leftEyeParams = _stereoConfig.GetEyeRenderParams(StereoEye_Left);
+    //Bind our framebuffer object. If we are rendering the glow effect, we let the glow effect shader take care of it
+    if (Menu::getInstance()->isOptionChecked(MenuOption::EnableGlowEffect)) {
+        Application::getInstance()->getGlowEffect()->prepare();
+    } else {
+        Application::getInstance()->getTextureCache()->getPrimaryFramebufferObject()->bind();
+        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+    }
+
+    ovrPosef eyeRenderPose[ovrEye_Count];
+
+    _camera->setTightness(0.0f);  //  In first person, camera follows (untweaked) head exactly without delay
+    _camera->setDistance(0.0f);
+    _camera->setUpShift(0.0f);
+
     glMatrixMode(GL_PROJECTION);
     glPushMatrix();
-    glLoadIdentity();
-    glTranslatef(_stereoConfig.GetProjectionCenterOffset(), 0, 0);
-    gluPerspective(whichCamera.getFieldOfView(), whichCamera.getAspectRatio(),
-        whichCamera.getNearClip(), whichCamera.getFarClip());
-    
-    glViewport(leftEyeParams.VP.x, leftEyeParams.VP.y, leftEyeParams.VP.w, leftEyeParams.VP.h);
+
     glMatrixMode(GL_MODELVIEW);
     glPushMatrix();
-    glLoadIdentity();
-    glTranslatef(_stereoConfig.GetIPD() * 0.5f, 0, 0);
-    
-    Application::getInstance()->displaySide(whichCamera);
+  
+    glm::quat orientation;
+  
+    //Render each eye into an fbo
+    for (int eyeIndex = 0; eyeIndex < ovrEye_Count; eyeIndex++) {
 
-    if (displayOverlays) {
-        applicationOverlay.displayOverlayTextureOculus(whichCamera);
-    }
-    
-    // and the right eye to the right side
-    const StereoEyeParams& rightEyeParams = _stereoConfig.GetEyeRenderParams(StereoEye_Right);
-    glMatrixMode(GL_PROJECTION);
-    glLoadIdentity();
-    glTranslatef(-_stereoConfig.GetProjectionCenterOffset(), 0, 0);
-    gluPerspective(whichCamera.getFieldOfView(), whichCamera.getAspectRatio(),
-        whichCamera.getNearClip(), whichCamera.getFarClip());
-    
-    glViewport(rightEyeParams.VP.x, rightEyeParams.VP.y, rightEyeParams.VP.w, rightEyeParams.VP.h);
-    glMatrixMode(GL_MODELVIEW);
-    glLoadIdentity();
-    glTranslatef(_stereoConfig.GetIPD() * -0.5f, 0, 0);
-    
-    Application::getInstance()->displaySide(whichCamera);
+        ovrEyeType eye = _ovrHmdDesc.EyeRenderOrder[eyeIndex];
 
-    if (displayOverlays) {
-        applicationOverlay.displayOverlayTextureOculus(whichCamera);
+        //Set the camera rotation for this eye
+        eyeRenderPose[eye] = ovrHmd_GetEyePose(_ovrHmd, eye);
+        orientation.x = eyeRenderPose[eye].Orientation.x;
+        orientation.y = eyeRenderPose[eye].Orientation.y;
+        orientation.z = eyeRenderPose[eye].Orientation.z;
+        orientation.w = eyeRenderPose[eye].Orientation.w;
+        
+        _camera->setTargetRotation(bodyOrientation * orientation);
+        _camera->setTargetPosition(position);
+        _camera->update(1.0f / Application::getInstance()->getFps());
+
+        Matrix4f proj = ovrMatrix4f_Projection(_eyeRenderDesc[eye].Fov, whichCamera.getNearClip(), whichCamera.getFarClip(), true);
+        proj.Transpose();
+
+        glMatrixMode(GL_PROJECTION);
+        glLoadIdentity();
+        glLoadMatrixf((GLfloat *)proj.M);
+
+        glViewport(_eyeRenderViewport[eye].Pos.x, _eyeRenderViewport[eye].Pos.y,
+                   _eyeRenderViewport[eye].Size.w, _eyeRenderViewport[eye].Size.h);
+
+        glMatrixMode(GL_MODELVIEW);
+        glLoadIdentity();
+        glTranslatef(_eyeRenderDesc[eye].ViewAdjust.x, _eyeRenderDesc[eye].ViewAdjust.y, _eyeRenderDesc[eye].ViewAdjust.z);
+
+        Application::getInstance()->displaySide(*_camera);
+
+        if (displayOverlays) {
+            applicationOverlay.displayOverlayTextureOculus(*_camera);
+        }
     }
-   
+
+    //Wait till time-warp to reduce latency
+    ovr_WaitTillTime(_hmdFrameTiming.TimewarpPointSeconds);
+
     glPopMatrix();
-    
-    // restore our normal viewport
-    const Viewport& fullViewport = _stereoConfig.GetFullViewport();
-    glViewport(fullViewport.x, fullViewport.y, fullViewport.w, fullViewport.h);
 
-    QOpenGLFramebufferObject* fbo = Application::getInstance()->getGlowEffect()->render(true);
-    glBindTexture(GL_TEXTURE_2D, fbo->texture());
+    //Full texture viewport for glow effect
+    glViewport(0, 0, _renderTargetSize.w, _renderTargetSize.h);
+  
+    //Bind the output texture from the glow shader. If glow effect is disabled, we just grab the texture
+    if (Menu::getInstance()->isOptionChecked(MenuOption::EnableGlowEffect)) {
+        QOpenGLFramebufferObject* fbo = Application::getInstance()->getGlowEffect()->render(true);
+        glBindTexture(GL_TEXTURE_2D, fbo->texture());
+    } else {
+        Application::getInstance()->getTextureCache()->getPrimaryFramebufferObject()->release();
+        glBindTexture(GL_TEXTURE_2D, Application::getInstance()->getTextureCache()->getPrimaryFramebufferObject()->texture());
+    }
+
+    // restore our normal viewport
+    glViewport(0, 0, Application::getInstance()->getGLWidget()->width(), Application::getInstance()->getGLWidget()->height());
 
     glMatrixMode(GL_PROJECTION);
+    glPopMatrix();
+
+    //Renders the distorted mesh onto the screen
+    renderDistortionMesh(eyeRenderPose);
+    
+    glBindTexture(GL_TEXTURE_2D, 0);
+    
+#endif
+}
+
+#ifdef HAVE_LIBOVR
+void OculusManager::renderDistortionMesh(ovrPosef eyeRenderPose[ovrEye_Count]) {
+
     glLoadIdentity();
-    gluOrtho2D(fullViewport.x, fullViewport.x + fullViewport.w, fullViewport.y, fullViewport.y + fullViewport.h);
+    gluOrtho2D(0, Application::getInstance()->getGLWidget()->width(), 0, Application::getInstance()->getGLWidget()->height());
+
     glDisable(GL_DEPTH_TEST);
-    
-    // for reference on setting these values, see SDK file Samples/OculusRoomTiny/RenderTiny_Device.cpp
-    
-    float scaleFactor = 1.0 / _stereoConfig.GetDistortionScale();
-    float aspectRatio = _stereoConfig.GetAspect();
-    
+
     glDisable(GL_BLEND);
     _program.bind();
     _program.setUniformValue(_textureLocation, 0);
-    const DistortionConfig& distortionConfig = _stereoConfig.GetDistortionConfig();
-    _program.setUniformValue(_lensCenterLocation, (0.5 + distortionConfig.XCenterOffset * 0.5) * 0.5, 0.5);
-    _program.setUniformValue(_screenCenterLocation, 0.25, 0.5);
-    _program.setUniformValue(_scaleLocation, 0.25 * scaleFactor, 0.5 * scaleFactor * aspectRatio);
-    _program.setUniformValue(_scaleInLocation, 4, 2 / aspectRatio);
-    _program.setUniformValue(_hmdWarpParamLocation, distortionConfig.K[0], distortionConfig.K[1],
-        distortionConfig.K[2], distortionConfig.K[3]);
 
-    glColor3f(1, 0, 1);
-    glBegin(GL_QUADS);
-    glTexCoord2f(0, 0);
-    glVertex2f(0, 0);
-    glTexCoord2f(0.5, 0);
-    glVertex2f(leftEyeParams.VP.w, 0);
-    glTexCoord2f(0.5, 1);
-    glVertex2f(leftEyeParams.VP.w, leftEyeParams.VP.h);
-    glTexCoord2f(0, 1);
-    glVertex2f(0, leftEyeParams.VP.h);
-    glEnd();
-    
-    _program.setUniformValue(_lensCenterLocation, 0.5 + (0.5 - distortionConfig.XCenterOffset * 0.5) * 0.5, 0.5);
-    _program.setUniformValue(_screenCenterLocation, 0.75, 0.5);
-    
-    glBegin(GL_QUADS);
-    glTexCoord2f(0.5, 0);
-    glVertex2f(leftEyeParams.VP.w, 0);
-    glTexCoord2f(1, 0);
-    glVertex2f(fullViewport.w, 0);
-    glTexCoord2f(1, 1);
-    glVertex2f(fullViewport.w, leftEyeParams.VP.h);
-    glTexCoord2f(0.5, 1);
-    glVertex2f(leftEyeParams.VP.w, leftEyeParams.VP.h);
-    glEnd();
-    
-    glEnable(GL_BLEND);           
-    glBindTexture(GL_TEXTURE_2D, 0);
+    _program.enableAttributeArray(_positionAttributeLocation);
+    _program.enableAttributeArray(_colorAttributeLocation);
+    _program.enableAttributeArray(_texCoord0AttributeLocation);
+    _program.enableAttributeArray(_texCoord1AttributeLocation);
+    _program.enableAttributeArray(_texCoord2AttributeLocation);
+
+    //Render the distortion meshes for each eye
+    for (int eyeNum = 0; eyeNum < ovrEye_Count; eyeNum++) {
+        GLfloat uvScale[2] = { _UVScaleOffset[eyeNum][0].x, _UVScaleOffset[eyeNum][0].y };
+        _program.setUniformValueArray(_eyeToSourceUVScaleLocation, uvScale, 1, 2);
+        GLfloat uvOffset[2] = { _UVScaleOffset[eyeNum][1].x, _UVScaleOffset[eyeNum][1].y };
+        _program.setUniformValueArray(_eyeToSourceUVOffsetLocation, uvOffset, 1, 2);
+
+        ovrMatrix4f timeWarpMatrices[2];
+        Matrix4f transposeMatrices[2];
+        //Grabs the timewarp matrices to be used in the shader
+        ovrHmd_GetEyeTimewarpMatrices(_ovrHmd, (ovrEyeType)eyeNum, eyeRenderPose[eyeNum], timeWarpMatrices);
+        transposeMatrices[0] = Matrix4f(timeWarpMatrices[0]);
+        transposeMatrices[1] = Matrix4f(timeWarpMatrices[1]);
+
+        //Have to transpose the matrices before using them
+        transposeMatrices[0].Transpose();
+        transposeMatrices[1].Transpose();
+
+        glUniformMatrix4fv(_eyeRotationStartLocation, 1, GL_FALSE, (GLfloat *)transposeMatrices[0].M);
+        glUniformMatrix4fv(_eyeRotationEndLocation, 1, GL_FALSE, (GLfloat *)transposeMatrices[1].M);
+
+        glBindBuffer(GL_ARRAY_BUFFER, _vertices[eyeNum]);
+
+        //Set vertex attribute pointers
+        glVertexAttribPointer(_positionAttributeLocation, 2, GL_FLOAT, GL_FALSE, sizeof(DistortionVertex), (void *)0);
+        glVertexAttribPointer(_texCoord0AttributeLocation, 2, GL_FLOAT, GL_FALSE, sizeof(DistortionVertex), (void *)8);
+        glVertexAttribPointer(_texCoord1AttributeLocation, 2, GL_FLOAT, GL_FALSE, sizeof(DistortionVertex), (void *)16);
+        glVertexAttribPointer(_texCoord2AttributeLocation, 2, GL_FLOAT, GL_FALSE, sizeof(DistortionVertex), (void *)24);
+        glVertexAttribPointer(_colorAttributeLocation, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(DistortionVertex), (void *)32);
+
+        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indices[eyeNum]);
+        glDrawElements(GL_TRIANGLES, _meshSize[eyeNum], GL_UNSIGNED_SHORT, 0);
+    }
+
+    _program.disableAttributeArray(_positionAttributeLocation);
+    _program.disableAttributeArray(_colorAttributeLocation);
+    _program.disableAttributeArray(_texCoord0AttributeLocation);
+    _program.disableAttributeArray(_texCoord1AttributeLocation);
+    _program.disableAttributeArray(_texCoord2AttributeLocation);
+
+    glEnable(GL_BLEND);
+    glEnable(GL_DEPTH_TEST);
     _program.release();
-    
-    glPopMatrix();
-#endif
+    glBindBuffer(GL_ARRAY_BUFFER, 0);
 }
+#endif
 
+//Tries to reconnect to the sensors
 void OculusManager::reset() {
 #ifdef HAVE_LIBOVR
-    _sensorFusion->Reset();
+    disconnect();
+    connect();
 #endif
 }
 
+//Gets the current predicted angles from the oculus sensors
 void OculusManager::getEulerAngles(float& yaw, float& pitch, float& roll) {
 #ifdef HAVE_LIBOVR
-    _sensorFusion->GetPredictedOrientation().GetEulerAngles<Axis_Y, Axis_X, Axis_Z, Rotate_CCW, Handed_R>(&yaw, &pitch, &roll);
+    ovrSensorState ss = ovrHmd_GetSensorState(_ovrHmd, _hmdFrameTiming.ScanoutMidpointSeconds);
+
+    if (ss.StatusFlags & (ovrStatus_OrientationTracked | ovrStatus_PositionTracked)) {
+        ovrPosef pose = ss.Predicted.Pose;
+        Quatf orientation = Quatf(pose.Orientation);
+        orientation.GetEulerAngles<Axis_Y, Axis_X, Axis_Z, Rotate_CCW, Handed_R>(&yaw, &pitch, &roll);
+    }
+#endif
+}
+
+//Used to set the size of the glow framebuffers
+QSize OculusManager::getRenderTargetSize() {
+#ifdef HAVE_LIBOVR
+    QSize rv;
+    rv.setWidth(_renderTargetSize.w);
+    rv.setHeight(_renderTargetSize.h);
+    return rv;
+#else
+    return QSize(100, 100);
 #endif
 }
 
diff --git a/interface/src/devices/OculusManager.h b/interface/src/devices/OculusManager.h
index 21b9d67f4d..7798875c2c 100644
--- a/interface/src/devices/OculusManager.h
+++ b/interface/src/devices/OculusManager.h
@@ -3,6 +3,7 @@
 //  interface/src/devices
 //
 //  Created by Stephen Birarda on 5/9/13.
+//  Refactored by Ben Arnold on 6/30/2014
 //  Copyright 2012 High Fidelity, Inc.
 //
 //  Distributed under the Apache License, Version 2.0.
@@ -12,10 +13,9 @@
 #ifndef hifi_OculusManager_h
 #define hifi_OculusManager_h
 
-#include <iostream>
-
 #ifdef HAVE_LIBOVR
 #include <OVR.h>
+#include "../src/Util/Util_Render_Stereo.h"
 #endif
 
 #include "renderer/ProgramObject.h"
@@ -28,38 +28,69 @@ class Camera;
 class OculusManager {
 public:
     static void connect();
-    
+    static void disconnect();
     static bool isConnected();
-    
+    static void beginFrameTiming();
+    static void endFrameTiming();
     static void configureCamera(Camera& camera, int screenWidth, int screenHeight);
-    
-    static void display(Camera& whichCamera);
-    
+    static void display(const glm::quat &bodyOrientation, const glm::vec3 &position, Camera& whichCamera);
     static void reset();
     
     /// param \yaw[out] yaw in radians
     /// param \pitch[out] pitch in radians
     /// param \roll[out] roll in radians
     static void getEulerAngles(float& yaw, float& pitch, float& roll);
-    
-    static void updateYawOffset();
+    static QSize getRenderTargetSize();
     
 private:
+#ifdef HAVE_LIBOVR
+    static void generateDistortionMesh();
+    static void renderDistortionMesh(ovrPosef eyeRenderPose[ovrEye_Count]);
+
+    struct DistortionVertex {
+        glm::vec2 pos;
+        glm::vec2 texR;
+        glm::vec2 texG;
+        glm::vec2 texB;
+        struct {
+            GLubyte r;
+            GLubyte g;
+            GLubyte b;
+            GLubyte a;
+        } color;
+    };
+
     static ProgramObject _program;
+    //Uniforms
     static int _textureLocation;
-    static int _lensCenterLocation;
-    static int _screenCenterLocation;
-    static int _scaleLocation;
-    static int _scaleInLocation;
-    static int _hmdWarpParamLocation;    
+    static int _eyeToSourceUVScaleLocation;
+    static int _eyeToSourceUVOffsetLocation;
+    static int _eyeRotationStartLocation;
+    static int _eyeRotationEndLocation;
+    //Attributes
+    static int _positionAttributeLocation;
+    static int _colorAttributeLocation;
+    static int _texCoord0AttributeLocation;
+    static int _texCoord1AttributeLocation;
+    static int _texCoord2AttributeLocation;
+
     static bool _isConnected;
     
-#ifdef HAVE_LIBOVR
-    static OVR::Ptr<OVR::DeviceManager> _deviceManager;
-    static OVR::Ptr<OVR::HMDDevice> _hmdDevice;
-    static OVR::Ptr<OVR::SensorDevice> _sensorDevice;
-    static OVR::SensorFusion* _sensorFusion;
-    static OVR::Util::Render::StereoConfig _stereoConfig;
+    static ovrHmd _ovrHmd;
+    static ovrHmdDesc _ovrHmdDesc;
+    static ovrFovPort _eyeFov[ovrEye_Count];
+    static ovrEyeRenderDesc _eyeRenderDesc[ovrEye_Count];
+    static ovrSizei _renderTargetSize;
+    static ovrVector2f _UVScaleOffset[ovrEye_Count][2];
+    static GLuint _vertices[ovrEye_Count];
+    static GLuint _indices[ovrEye_Count];
+    static GLsizei _meshSize[ovrEye_Count];
+    static ovrFrameTiming _hmdFrameTiming;
+    static ovrRecti _eyeRenderViewport[ovrEye_Count];
+    static unsigned int _frameIndex;
+    static bool _frameTimingActive;
+    static bool _programInitialized;
+    static Camera* _camera;
 #endif
 };
 
diff --git a/interface/src/devices/SixenseManager.cpp b/interface/src/devices/SixenseManager.cpp
index 07536d0af8..1b7baf2ee1 100644
--- a/interface/src/devices/SixenseManager.cpp
+++ b/interface/src/devices/SixenseManager.cpp
@@ -13,6 +13,7 @@
 
 #include "Application.h"
 #include "SixenseManager.h"
+#include "UserActivityLogger.h"
 
 #ifdef HAVE_SIXENSE
 const int CALIBRATION_STATE_IDLE = 0;
@@ -39,6 +40,7 @@ SixenseManager::SixenseManager() {
 
     sixenseInit();
 #endif
+    _hydrasConnected = false;
     _triggerPressed[0] = false;
     _bumperPressed[0] = false;
     _oldX[0] = -1;
@@ -70,7 +72,11 @@ void SixenseManager::setFilter(bool filter) {
 void SixenseManager::update(float deltaTime) {
 #ifdef HAVE_SIXENSE
     if (sixenseGetNumActiveControllers() == 0) {
+        _hydrasConnected = false;
         return;
+    } else if (!_hydrasConnected) {
+        _hydrasConnected = true;
+        UserActivityLogger::getInstance().connectedDevice("spatial_controller", "hydra");
     }
     MyAvatar* avatar = Application::getInstance()->getAvatar();
     Hand* hand = avatar->getHand();
diff --git a/interface/src/devices/SixenseManager.h b/interface/src/devices/SixenseManager.h
index 8803c2c006..8ca27ef77c 100644
--- a/interface/src/devices/SixenseManager.h
+++ b/interface/src/devices/SixenseManager.h
@@ -71,6 +71,7 @@ private:
     float _lastDistance;
 
 #endif
+    bool _hydrasConnected;
     quint64 _lastMovement;
     glm::vec3 _amountMoved;
 
diff --git a/interface/src/location/LocationManager.cpp b/interface/src/location/LocationManager.cpp
index 1d783cc8e7..32172d6e38 100644
--- a/interface/src/location/LocationManager.cpp
+++ b/interface/src/location/LocationManager.cpp
@@ -15,7 +15,7 @@
 #include "LocationManager.h"
 
 const QString GET_USER_ADDRESS = "/api/v1/users/%1/address";
-const QString GET_PLACE_ADDRESS = "/api/v1/places/%1/address";
+const QString GET_PLACE_ADDRESS = "/api/v1/places/%1";
 const QString GET_ADDRESSES = "/api/v1/addresses/%1";
 const QString POST_PLACE_CREATE = "/api/v1/places/";
 
diff --git a/interface/src/models/ModelTreeRenderer.cpp b/interface/src/models/ModelTreeRenderer.cpp
index 9c4b08fd99..78107db699 100644
--- a/interface/src/models/ModelTreeRenderer.cpp
+++ b/interface/src/models/ModelTreeRenderer.cpp
@@ -135,7 +135,7 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args)
     args->_elementsTouched++;
     // actually render it here...
     // we need to iterate the actual modelItems of the element
-    ModelTreeElement* modelTreeElement = (ModelTreeElement*)element;
+    ModelTreeElement* modelTreeElement = static_cast<ModelTreeElement*>(element);
 
     QList<ModelItem>& modelItems = modelTreeElement->getModels();
 
diff --git a/interface/src/renderer/GlowEffect.cpp b/interface/src/renderer/GlowEffect.cpp
index 262a632df0..c163136956 100644
--- a/interface/src/renderer/GlowEffect.cpp
+++ b/interface/src/renderer/GlowEffect.cpp
@@ -180,7 +180,7 @@ QOpenGLFramebufferObject* GlowEffect::render(bool toTexture) {
             glBindTexture(GL_TEXTURE_2D, oldDiffusedFBO->texture());
             
             _diffuseProgram->bind();
-            QSize size = Application::getInstance()->getGLWidget()->size();
+            QSize size = primaryFBO->size();
             _diffuseProgram->setUniformValue(_diffusionScaleLocation, 1.0f / size.width(), 1.0f / size.height());
         
             renderFullscreenQuad();
diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp
index aff023c2a0..3b5cda4fd2 100644
--- a/interface/src/renderer/Model.cpp
+++ b/interface/src/renderer/Model.cpp
@@ -16,15 +16,15 @@
 #include <glm/gtx/transform.hpp>
 #include <glm/gtx/norm.hpp>
 
+#include <CapsuleShape.h>
 #include <GeometryUtil.h>
+#include <PhysicsEntity.h>
+#include <ShapeCollider.h>
+#include <SphereShape.h>
 
 #include "Application.h"
 #include "Model.h"
 
-#include <SphereShape.h>
-#include <CapsuleShape.h>
-#include <ShapeCollider.h>
-
 using namespace std;
 
 static int modelPointerTypeId = qRegisterMetaType<QPointer<Model> >();
@@ -40,10 +40,7 @@ Model::Model(QObject* parent) :
     _snapModelToCenter(false),
     _snappedToCenter(false),
     _rootIndex(-1),
-    _shapesAreDirty(true),
-    _boundingRadius(0.0f),
-    _boundingShape(),
-    _boundingShapeLocalOffset(0.0f),
+    //_enableCollisionShapes(false),
     _lodDistance(0.0f),
     _pupilDilation(0.0f),
     _url("http://invalid.com") {
@@ -129,7 +126,10 @@ void Model::setScaleInternal(const glm::vec3& scale) {
     const float ONE_PERCENT = 0.01f;
     if (relativeDeltaScale > ONE_PERCENT || scaleLength < EPSILON) {
         _scale = scale;
-        rebuildShapes();
+        if (_shapes.size() > 0) {
+            clearShapes();
+            buildShapes();
+        }
     }
 }
 
@@ -174,6 +174,7 @@ QVector<JointState> Model::createJointStates(const FBXGeometry& geometry) {
         int parentIndex = joint.parentIndex;
         if (parentIndex == -1) {
             _rootIndex = i;
+            // NOTE: in practice geometry.offset has a non-unity scale (rather than a translation)
             glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
             state.computeTransform(parentTransform);
         } else {
@@ -551,7 +552,6 @@ bool Model::updateGeometry() {
             model->setURL(attachment.url);
             _attachments.append(model);
         }
-        rebuildShapes();
         needFullUpdate = true;
     }
     return needFullUpdate;
@@ -560,6 +560,18 @@ bool Model::updateGeometry() {
 // virtual
 void Model::setJointStates(QVector<JointState> states) {
     _jointStates = states;
+
+    // compute an approximate bounding radius for broadphase collision queries
+    // against PhysicsSimulation boundaries
+    int numJoints = _jointStates.size();
+    float radius = 0.0f;
+    for (int i = 0; i < numJoints; ++i) {
+        float distance = glm::length(_jointStates[i].getPosition());
+        if (distance > radius) {
+            radius = distance;
+        }
+    }
+    _boundingRadius = radius;
 }
 
 bool Model::render(float alpha, RenderMode mode, bool receiveShadows) {
@@ -774,304 +786,13 @@ AnimationHandlePointer Model::createAnimationHandle() {
     return handle;
 }
 
-void Model::clearShapes() {
-    for (int i = 0; i < _jointShapes.size(); ++i) {
-        delete _jointShapes[i];
-    }
-    _jointShapes.clear();
-}
-
-void Model::rebuildShapes() {
-    clearShapes();
-    
-    if (!_geometry || _rootIndex == -1) {
-        return;
-    }
-    
-    const FBXGeometry& geometry = _geometry->getFBXGeometry();
-    if (geometry.joints.isEmpty()) {
-        return;
-    }
-
-    // We create the shapes with proper dimensions, but we set their transforms later.
-    float uniformScale = extractUniformScale(_scale);
-    for (int i = 0; i < _jointStates.size(); i++) {
-        const FBXJoint& joint = geometry.joints[i];
-
-        float radius = uniformScale * joint.boneRadius;
-        float halfHeight = 0.5f * uniformScale * joint.distanceToParent;
-        Shape::Type type = joint.shapeType;
-        if (type == Shape::CAPSULE_SHAPE && halfHeight < EPSILON) {
-            // this capsule is effectively a sphere
-            type = Shape::SPHERE_SHAPE;
-        }
-        if (type == Shape::CAPSULE_SHAPE) {
-            CapsuleShape* capsule = new CapsuleShape(radius, halfHeight);
-            _jointShapes.push_back(capsule);
-        } else if (type == Shape::SPHERE_SHAPE) {
-            SphereShape* sphere = new SphereShape(radius, glm::vec3(0.0f));
-            _jointShapes.push_back(sphere);
-        } else {
-            // this shape type is not handled and the joint shouldn't collide, 
-            // however we must have a shape for each joint, 
-            // so we make a bogus sphere with zero radius.
-            // TODO: implement collision groups for more control over what collides with what
-            SphereShape* sphere = new SphereShape(0.0f, glm::vec3(0.0f)); 
-            _jointShapes.push_back(sphere);
-        }
-    }
-
-    // This method moves the shapes to their default positions in Model frame
-    // which is where we compute the bounding shape's parameters.
-    computeBoundingShape(geometry);
-
-    // finally sync shapes to joint positions
-    _shapesAreDirty = true;
-    updateShapePositions();
-}
-
-void Model::computeBoundingShape(const FBXGeometry& geometry) {
-    // compute default joint transforms and rotations 
-    // (in local frame, ignoring Model translation and rotation)
-    int numJoints = geometry.joints.size();
-    QVector<glm::mat4> transforms;
-    transforms.fill(glm::mat4(), numJoints);
-    QVector<glm::quat> finalRotations;
-    finalRotations.fill(glm::quat(), numJoints);
-
-    QVector<bool> shapeIsSet;
-    shapeIsSet.fill(false, numJoints);
-    int numShapesSet = 0;
-    int lastNumShapesSet = -1;
-    while (numShapesSet < numJoints && numShapesSet != lastNumShapesSet) {
-        lastNumShapesSet = numShapesSet;
-        for (int i = 0; i < numJoints; i++) {
-            const FBXJoint& joint = geometry.joints.at(i);
-            int parentIndex = joint.parentIndex;
-            
-            if (parentIndex == -1) {
-                glm::mat4 baseTransform = glm::scale(_scale) * glm::translate(_offset);
-                glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation;    
-                glm::mat4 rootTransform = baseTransform * geometry.offset * glm::translate(joint.translation) 
-                    * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform;
-                // remove the tranlsation part before we save the root transform
-                transforms[i] = glm::translate(- extractTranslation(rootTransform)) * rootTransform;
-
-                finalRotations[i] = combinedRotation;
-                ++numShapesSet;
-                shapeIsSet[i] = true;
-            } else if (shapeIsSet[parentIndex]) {
-                glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation;    
-                transforms[i] = transforms[parentIndex] * glm::translate(joint.translation) 
-                    * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform;
-                finalRotations[i] = finalRotations[parentIndex] * combinedRotation;
-                ++numShapesSet;
-                shapeIsSet[i] = true;
-            }
-        }
-    }
-
-    // sync shapes to joints
-    _boundingRadius = 0.0f;
-    float uniformScale = extractUniformScale(_scale);
-    for (int i = 0; i < _jointShapes.size(); i++) {
-        const FBXJoint& joint = geometry.joints[i];
-        glm::vec3 jointToShapeOffset = uniformScale * (finalRotations[i] * joint.shapePosition);
-        glm::vec3 localPosition = extractTranslation(transforms[i]) + jointToShapeOffset;
-        Shape* shape = _jointShapes[i];
-        shape->setPosition(localPosition);
-        shape->setRotation(finalRotations[i] * joint.shapeRotation);
-        float distance = glm::length(localPosition) + shape->getBoundingRadius();
-        if (distance > _boundingRadius) {
-            _boundingRadius = distance;
-        }
-    }
-
-    // compute bounding box
-    Extents totalExtents;
-    totalExtents.reset();
-    for (int i = 0; i < _jointShapes.size(); i++) {
-        Extents shapeExtents;
-        shapeExtents.reset();
-
-        Shape* shape = _jointShapes[i];
-        glm::vec3 localPosition = shape->getPosition();
-        int type = shape->getType();
-        if (type == Shape::CAPSULE_SHAPE) {
-            // add the two furthest surface points of the capsule
-            CapsuleShape* capsule = static_cast<CapsuleShape*>(shape);
-            glm::vec3 axis;
-            capsule->computeNormalizedAxis(axis);
-            float radius = capsule->getRadius();
-            float halfHeight = capsule->getHalfHeight();
-            axis = halfHeight * axis + glm::vec3(radius);
-
-            shapeExtents.addPoint(localPosition + axis);
-            shapeExtents.addPoint(localPosition - axis);
-            totalExtents.addExtents(shapeExtents);
-        } else if (type == Shape::SPHERE_SHAPE) {
-            float radius = shape->getBoundingRadius();
-            glm::vec3 axis = glm::vec3(radius);
-            shapeExtents.addPoint(localPosition + axis);
-            shapeExtents.addPoint(localPosition - axis);
-            totalExtents.addExtents(shapeExtents);
-        }
-    }
-
-    // compute bounding shape parameters
-    // NOTE: we assume that the longest side of totalExtents is the yAxis...
-    glm::vec3 diagonal = totalExtents.maximum - totalExtents.minimum;
-    // ... and assume the radius is half the RMS of the X and Z sides:
-    float capsuleRadius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z));
-    _boundingShape.setRadius(capsuleRadius);
-    _boundingShape.setHalfHeight(0.5f * diagonal.y - capsuleRadius);
-    _boundingShapeLocalOffset = 0.5f * (totalExtents.maximum + totalExtents.minimum);
-}
-
-void Model::resetShapePositions() {
-    // DEBUG method.
-    // Moves shapes to the joint default locations for debug visibility into
-    // how the bounding shape is computed.
-
-    if (!_geometry || _rootIndex == -1) {
-        // geometry or joints have not yet been created
-        return;
-    }
-    
-    const FBXGeometry& geometry = _geometry->getFBXGeometry();
-    if (geometry.joints.isEmpty() || _jointShapes.size() != geometry.joints.size()) {
-        return;
-    }
-
-    // The shapes are moved to their default positions in computeBoundingShape().
-    computeBoundingShape(geometry);
-
-    // Then we move them into world frame for rendering at the Model's location.
-    for (int i = 0; i < _jointShapes.size(); i++) {
-        Shape* shape = _jointShapes[i];
-        shape->setPosition(_translation + _rotation * shape->getPosition());
-        shape->setRotation(_rotation * shape->getRotation());
-    }
-    _boundingShape.setPosition(_translation + _rotation * _boundingShapeLocalOffset);
-    _boundingShape.setRotation(_rotation);
+// virtual override from PhysicsEntity
+void Model::buildShapes() {
+    // TODO: figure out how to load/build collision shapes for general models
 }
 
 void Model::updateShapePositions() {
-    if (_shapesAreDirty && _jointShapes.size() == _jointStates.size()) {
-        glm::vec3 rootPosition(0.0f);
-        _boundingRadius = 0.0f;
-        float uniformScale = extractUniformScale(_scale);
-        for (int i = 0; i < _jointStates.size(); i++) {
-            const JointState& state = _jointStates[i];
-            const FBXJoint& joint = state.getFBXJoint();
-            // shape position and rotation need to be in world-frame
-            glm::quat stateRotation = state.getRotation();
-            glm::vec3 shapeOffset = uniformScale * (stateRotation * joint.shapePosition);
-            glm::vec3 worldPosition = _translation + _rotation * (state.getPosition() + shapeOffset);
-            Shape* shape = _jointShapes[i];
-            shape->setPosition(worldPosition);
-            shape->setRotation(_rotation * stateRotation * joint.shapeRotation);
-            float distance = glm::distance(worldPosition, _translation) + shape->getBoundingRadius();
-            if (distance > _boundingRadius) {
-                _boundingRadius = distance;
-            }
-            if (joint.parentIndex == -1) {
-                rootPosition = worldPosition;
-            }
-        }
-        _shapesAreDirty = false;
-        _boundingShape.setPosition(rootPosition + _rotation * _boundingShapeLocalOffset);
-        _boundingShape.setRotation(_rotation);
-    }
-}
-
-bool Model::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const {
-    const glm::vec3 relativeOrigin = origin - _translation;
-    const FBXGeometry& geometry = _geometry->getFBXGeometry();
-    float minDistance = FLT_MAX;
-    float radiusScale = extractUniformScale(_scale);
-    for (int i = 0; i < _jointStates.size(); i++) {
-        const FBXJoint& joint = geometry.joints[i];
-        glm::vec3 end = _translation + _rotation * _jointStates[i].getPosition();
-        float endRadius = joint.boneRadius * radiusScale;
-        glm::vec3 start = end;
-        float startRadius = joint.boneRadius * radiusScale;
-        if (joint.parentIndex != -1) {
-            start = _translation + _rotation * _jointStates[joint.parentIndex].getPosition();
-            startRadius = geometry.joints[joint.parentIndex].boneRadius * radiusScale;
-        }
-        // for now, use average of start and end radii
-        float capsuleDistance;
-        if (findRayCapsuleIntersection(relativeOrigin, direction, start, end,
-                (startRadius + endRadius) / 2.0f, capsuleDistance)) {
-            minDistance = qMin(minDistance, capsuleDistance);
-        }
-    }
-    if (minDistance < FLT_MAX) {
-        distance = minDistance;
-        return true;
-    }
-    return false;
-}
-
-bool Model::findCollisions(const QVector<const Shape*> shapes, CollisionList& collisions) {
-    bool collided = false;
-    for (int i = 0; i < shapes.size(); ++i) {
-        const Shape* theirShape = shapes[i];
-        for (int j = 0; j < _jointShapes.size(); ++j) {
-            const Shape* ourShape = _jointShapes[j];
-            if (ShapeCollider::collideShapes(theirShape, ourShape, collisions)) {
-                collided = true;
-            }
-        }
-    }
-    return collided;
-}
-
-bool Model::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadius,
-    CollisionList& collisions, int skipIndex) {
-    bool collided = false;
-    SphereShape sphere(sphereRadius, sphereCenter);
-    const FBXGeometry& geometry = _geometry->getFBXGeometry();
-    for (int i = 0; i < _jointShapes.size(); i++) {
-        const FBXJoint& joint = geometry.joints[i];
-        if (joint.parentIndex != -1) {
-            if (skipIndex != -1) {
-                int ancestorIndex = joint.parentIndex;
-                do {
-                    if (ancestorIndex == skipIndex) {
-                        goto outerContinue;
-                    }
-                    ancestorIndex = geometry.joints[ancestorIndex].parentIndex;
-                    
-                } while (ancestorIndex != -1);
-            }
-        }
-        if (ShapeCollider::collideShapes(&sphere, _jointShapes[i], collisions)) {
-            CollisionInfo* collision = collisions.getLastCollision();
-            collision->_type = COLLISION_TYPE_MODEL;
-            collision->_data = (void*)(this);
-            collision->_intData = i;
-            collided = true;
-        }
-        outerContinue: ;
-    }
-    return collided;
-}
-
-bool Model::findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions) {
-    bool collided = false;
-    PlaneShape planeShape(plane);
-    for (int i = 0; i < _jointShapes.size(); i++) {
-        if (ShapeCollider::collideShapes(&planeShape, _jointShapes[i], collisions)) {
-            CollisionInfo* collision = collisions.getLastCollision();
-            collision->_type = COLLISION_TYPE_MODEL;
-            collision->_data = (void*)(this);
-            collision->_intData = i;
-            collided = true;
-        }
-    }
-    return collided;
+    // TODO: implement this when we know how to build shapes for regular Models
 }
 
 class Blender : public QRunnable {
@@ -1197,7 +918,7 @@ void Model::simulateInternal(float deltaTime) {
     for (int i = 0; i < _jointStates.size(); i++) {
         updateJointState(i);
     }
-    _shapesAreDirty = true;
+    _shapesAreDirty = ! _shapes.isEmpty();
     
     // update the attachment transforms and simulate them
     const FBXGeometry& geometry = _geometry->getFBXGeometry();
@@ -1332,7 +1053,7 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& position, const gl
     for (int j = freeLineage.size() - 1; j >= 0; j--) {
         updateJointState(freeLineage.at(j));
     }
-    _shapesAreDirty = true;
+    _shapesAreDirty = !_shapes.isEmpty();
         
     return true;
 }
@@ -1370,14 +1091,17 @@ const int BALL_SUBDIVISIONS = 10;
 void Model::renderJointCollisionShapes(float alpha) {
     glPushMatrix();
     Application::getInstance()->loadTranslatedViewMatrix(_translation);
-    for (int i = 0; i < _jointShapes.size(); i++) {
-        glPushMatrix();
+    for (int i = 0; i < _shapes.size(); i++) {
+        Shape* shape = _shapes[i];
+        if (!shape) {
+            continue;
+        }
 
-        Shape* shape = _jointShapes[i];
-        
+        glPushMatrix();
+        // NOTE: the shapes are in the avatar local-frame
         if (shape->getType() == Shape::SPHERE_SHAPE) {
             // shapes are stored in world-frame, so we have to transform into model frame
-            glm::vec3 position = shape->getPosition() - _translation;
+            glm::vec3 position = _rotation * shape->getTranslation();
             glTranslatef(position.x, position.y, position.z);
             const glm::quat& rotation = shape->getRotation();
             glm::vec3 axis = glm::axis(rotation);
@@ -1392,7 +1116,7 @@ void Model::renderJointCollisionShapes(float alpha) {
             // draw a blue sphere at the capsule endpoint
             glm::vec3 endPoint;
             capsule->getEndPoint(endPoint);
-            endPoint = endPoint - _translation;
+            endPoint = _rotation * endPoint;
             glTranslatef(endPoint.x, endPoint.y, endPoint.z);
             glColor4f(0.6f, 0.6f, 0.8f, alpha);
             glutSolidSphere(capsule->getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
@@ -1400,7 +1124,7 @@ void Model::renderJointCollisionShapes(float alpha) {
             // draw a yellow sphere at the capsule startpoint
             glm::vec3 startPoint;
             capsule->getStartPoint(startPoint);
-            startPoint = startPoint - _translation;
+            startPoint = _rotation * startPoint;
             glm::vec3 axis = endPoint - startPoint;
             glTranslatef(-axis.x, -axis.y, -axis.z);
             glColor4f(0.8f, 0.8f, 0.6f, alpha);
@@ -1416,85 +1140,6 @@ void Model::renderJointCollisionShapes(float alpha) {
     glPopMatrix();
 }
 
-void Model::renderBoundingCollisionShapes(float alpha) {
-    glPushMatrix();
-
-    Application::getInstance()->loadTranslatedViewMatrix(_translation);
-
-    // draw a blue sphere at the capsule endpoint
-    glm::vec3 endPoint;
-    _boundingShape.getEndPoint(endPoint);
-    endPoint = endPoint - _translation;
-    glTranslatef(endPoint.x, endPoint.y, endPoint.z);
-    glColor4f(0.6f, 0.6f, 0.8f, alpha);
-    glutSolidSphere(_boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
-
-    // draw a yellow sphere at the capsule startpoint
-    glm::vec3 startPoint;
-    _boundingShape.getStartPoint(startPoint);
-    startPoint = startPoint - _translation;
-    glm::vec3 axis = endPoint - startPoint;
-    glTranslatef(-axis.x, -axis.y, -axis.z);
-    glColor4f(0.8f, 0.8f, 0.6f, alpha);
-    glutSolidSphere(_boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS);
-
-    // draw a green cylinder between the two points
-    glm::vec3 origin(0.0f);
-    glColor4f(0.6f, 0.8f, 0.6f, alpha);
-    Avatar::renderJointConnectingCone( origin, axis, _boundingShape.getRadius(), _boundingShape.getRadius());
-
-    glPopMatrix();
-}
-
-bool Model::collisionHitsMoveableJoint(CollisionInfo& collision) const {
-    if (collision._type == COLLISION_TYPE_MODEL) {
-        // the joint is pokable by a collision if it exists and is free to move
-        const FBXJoint& joint = _geometry->getFBXGeometry().joints[collision._intData];
-        if (joint.parentIndex == -1 || _jointStates.isEmpty()) {
-            return false;
-        }
-        // an empty freeLineage means the joint can't move
-        const FBXGeometry& geometry = _geometry->getFBXGeometry();
-        int jointIndex = collision._intData;
-        const QVector<int>& freeLineage = geometry.joints.at(jointIndex).freeLineage;
-        return !freeLineage.isEmpty();
-    }
-    return false;
-}
-
-void Model::applyCollision(CollisionInfo& collision) {
-    if (collision._type != COLLISION_TYPE_MODEL) {
-        return;
-    }
-
-    glm::vec3 jointPosition(0.0f);
-    int jointIndex = collision._intData;
-    if (getJointPositionInWorldFrame(jointIndex, jointPosition)) {
-        const FBXJoint& joint = _geometry->getFBXGeometry().joints[jointIndex];
-        if (joint.parentIndex != -1) {
-            // compute the approximate distance (travel) that the joint needs to move
-            glm::vec3 start;
-            getJointPositionInWorldFrame(joint.parentIndex, start);
-            glm::vec3 contactPoint = collision._contactPoint - start;
-            glm::vec3 penetrationEnd = contactPoint + collision._penetration;
-            glm::vec3 axis = glm::cross(contactPoint, penetrationEnd);
-            float travel = glm::length(axis);
-            const float MIN_TRAVEL = 1.0e-8f;
-            if (travel > MIN_TRAVEL) {
-                // compute the new position of the joint
-                float angle = asinf(travel / (glm::length(contactPoint) * glm::length(penetrationEnd)));
-                axis = glm::normalize(axis);
-                glm::vec3 end;
-                getJointPositionInWorldFrame(jointIndex, end);
-                // transform into model-frame
-                glm::vec3 newEnd = glm::inverse(_rotation) * (start + glm::angleAxis(angle, axis) * (end - start) - _translation);
-                // try to move it
-                setJointPosition(jointIndex, newEnd, glm::quat(), false, -1, true);
-            }
-        }
-    }
-}
-
 void Model::setBlendedVertices(const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals) {
     if (_blendedVertexBuffers.isEmpty()) {
         return;
diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h
index 11e6861775..2045a0c9b5 100644
--- a/interface/src/renderer/Model.h
+++ b/interface/src/renderer/Model.h
@@ -16,7 +16,7 @@
 #include <QObject>
 #include <QUrl>
 
-#include <CapsuleShape.h>
+#include <PhysicsEntity.h>
 
 #include <AnimationCache.h>
 
@@ -33,7 +33,7 @@ typedef QSharedPointer<AnimationHandle> AnimationHandlePointer;
 typedef QWeakPointer<AnimationHandle> WeakAnimationHandlePointer;
 
 /// A generic 3D model displaying geometry loaded from a URL.
-class Model : public QObject {
+class Model : public QObject, public PhysicsEntity {
     Q_OBJECT
     
 public:
@@ -41,12 +41,6 @@ public:
     Model(QObject* parent = NULL);
     virtual ~Model();
     
-    void setTranslation(const glm::vec3& translation) { _translation = translation; }
-    const glm::vec3& getTranslation() const { return _translation; }
-    
-    void setRotation(const glm::quat& rotation) { _rotation = rotation; }
-    const glm::quat& getRotation() const { return _rotation; }
-    
     /// enables/disables scale to fit behavior, the model will be automatically scaled to the specified largest dimension
     void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f);
     bool getScaleToFit() const { return _scaleToFit; } /// is scale to fit enabled
@@ -67,7 +61,7 @@ public:
     
     void setBlendshapeCoefficients(const QVector<float>& coefficients) { _blendshapeCoefficients = coefficients; }
     const QVector<float>& getBlendshapeCoefficients() const { return _blendshapeCoefficients; }
-    
+
     bool isActive() const { return _geometry && _geometry->isLoaded(); }
     
     bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && _geometry->getMeshes().isEmpty()); }
@@ -134,69 +128,34 @@ public:
     QStringList getJointNames() const;
     
     AnimationHandlePointer createAnimationHandle();
-    
+
     const QList<AnimationHandlePointer>& getRunningAnimations() const { return _runningAnimations; }
-    
-    void clearShapes();
-    void rebuildShapes();
-    void resetShapePositions();
+   
+    // virtual overrides from PhysicsEntity
+    virtual void buildShapes();
     virtual void updateShapePositions();
+
     void renderJointCollisionShapes(float alpha);
-    void renderBoundingCollisionShapes(float alpha);
     
-    bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
-    
-    /// \param shapes list of pointers shapes to test against Model
-    /// \param collisions list to store collision results
-    /// \return true if at least one shape collided agains Model
-    bool findCollisions(const QVector<const Shape*> shapes, CollisionList& collisions);
-
-    bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius,
-        CollisionList& collisions, int skipIndex = -1);
-
-    bool findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions);
-    
-    /// \param collision details about the collisions
-    /// \return true if the collision is against a moveable joint
-    bool collisionHitsMoveableJoint(CollisionInfo& collision) const;
-
-    /// \param collision details about the collision
-    /// Use the collision to affect the model
-    void applyCollision(CollisionInfo& collision);
-
-    float getBoundingRadius() const { return _boundingRadius; }
-    float getBoundingShapeRadius() const { return _boundingShape.getRadius(); }
-
     /// Sets blended vertices computed in a separate thread.
     void setBlendedVertices(const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals);
 
-    const CapsuleShape& getBoundingShape() const { return _boundingShape; }
-
 protected:
-
     QSharedPointer<NetworkGeometry> _geometry;
     
-    glm::vec3 _translation;
-    glm::quat _rotation;
     glm::vec3 _scale;
     glm::vec3 _offset;
 
     bool _scaleToFit; /// If you set scaleToFit, we will calculate scale based on MeshExtents
     float _scaleToFitLargestDimension; /// this is the dimension that scale to fit will use
     bool _scaledToFit; /// have we scaled to fit
-    
+
     bool _snapModelToCenter; /// is the model's offset automatically adjusted to center around 0,0,0 in model space
     bool _snappedToCenter; /// are we currently snapped to center
     int _rootIndex;
     
-    bool _shapesAreDirty;
     QVector<JointState> _jointStates;
-    QVector<Shape*> _jointShapes;
-    
-    float _boundingRadius;
-    CapsuleShape _boundingShape;
-    glm::vec3 _boundingShapeLocalOffset;
-    
+
     class MeshState {
     public:
         QVector<glm::mat4> clusterMatrices;
@@ -240,8 +199,6 @@ protected:
     /// first free ancestor.
     float getLimbLength(int jointIndex) const;
 
-    void computeBoundingShape(const FBXGeometry& geometry);
-
 private:
     
     friend class AnimationHandle;
diff --git a/interface/src/renderer/RagDoll.cpp b/interface/src/renderer/RagDoll.cpp
deleted file mode 100644
index 305724d6e4..0000000000
--- a/interface/src/renderer/RagDoll.cpp
+++ /dev/null
@@ -1,167 +0,0 @@
-//
-//  RagDoll.cpp
-//  interface/src/avatar
-//
-//  Created by Andrew Meadows 2014.05.30
-//  Copyright 2014 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
-//
-
-#include <glm/glm.hpp>
-#include <glm/gtx/quaternion.hpp>
-#include <glm/gtx/transform.hpp>
-
-#include <CollisionInfo.h>
-#include <SharedUtil.h>
-#include <CapsuleShape.h>
-#include <SphereShape.h>
-
-#include "RagDoll.h"
-
-// ----------------------------------------------------------------------------
-// FixedConstraint
-// ----------------------------------------------------------------------------
-FixedConstraint::FixedConstraint(glm::vec3* point, const glm::vec3& anchor) : _point(point), _anchor(anchor) {
-}
-
-float FixedConstraint::enforce() {
-    assert(_point != NULL);
-    float distance = glm::distance(_anchor, *_point);
-    *_point = _anchor;
-    return distance;
-}
-
-void FixedConstraint::setPoint(glm::vec3* point) {
-    _point = point;
-}
-
-void FixedConstraint::setAnchor(const glm::vec3& anchor) {
-    _anchor = anchor;
-}
-
-// ----------------------------------------------------------------------------
-// DistanceConstraint
-// ----------------------------------------------------------------------------
-DistanceConstraint::DistanceConstraint(glm::vec3* startPoint, glm::vec3* endPoint) : _distance(-1.0f) {
-    _points[0] = startPoint;
-    _points[1] = endPoint;
-    _distance = glm::distance(*(_points[0]), *(_points[1]));
-}
-
-DistanceConstraint::DistanceConstraint(const DistanceConstraint& other) {
-    _distance = other._distance;
-    _points[0] = other._points[0];
-    _points[1] = other._points[1];
-}
-
-void DistanceConstraint::setDistance(float distance) {
-    _distance = fabsf(distance);
-}
-
-float DistanceConstraint::enforce() {
-    float newDistance = glm::distance(*(_points[0]), *(_points[1]));
-    glm::vec3 direction(0.0f, 1.0f, 0.0f);
-    if (newDistance > EPSILON) {
-        direction = (*(_points[0]) - *(_points[1])) / newDistance;
-    }
-    glm::vec3 center = 0.5f * (*(_points[0]) + *(_points[1]));
-    *(_points[0]) = center + (0.5f * _distance) * direction;
-    *(_points[1]) = center - (0.5f * _distance) * direction;
-    return glm::abs(newDistance - _distance);
-}
-
-void DistanceConstraint::updateProxyShape(Shape* shape, const glm::quat& rotation, const glm::vec3& translation) const {
-    if (!shape) {
-        return;
-    }
-    switch (shape->getType()) {
-        case Shape::SPHERE_SHAPE: {
-            // sphere collides at endPoint
-            SphereShape* sphere = static_cast<SphereShape*>(shape);
-            sphere->setPosition(translation + rotation * (*_points[1]));
-        }
-        break;
-        case Shape::CAPSULE_SHAPE: {
-            // capsule collides from startPoint to endPoint
-            CapsuleShape* capsule = static_cast<CapsuleShape*>(shape);
-            capsule->setEndPoints(translation + rotation * (*_points[0]), translation + rotation * (*_points[1]));
-        }
-        break;
-        default:
-        break;
-    }
-}
-
-// ----------------------------------------------------------------------------
-// RagDoll
-// ----------------------------------------------------------------------------
-
-RagDoll::RagDoll() {
-}
-
-RagDoll::~RagDoll() {
-    clear();
-}
-    
-void RagDoll::init(const QVector<JointState>& states) {
-    clear();
-    const int numStates = states.size();
-    _points.reserve(numStates);
-    for (int i = 0; i < numStates; ++i) {
-        const JointState& state = states[i];
-        _points.push_back(state.getPosition());
-        int parentIndex = state.getFBXJoint().parentIndex;
-        assert(parentIndex < i);
-        if (parentIndex == -1) {
-            FixedConstraint* anchor = new FixedConstraint(&(_points[i]), glm::vec3(0.0f));
-            _constraints.push_back(anchor);
-        } else {
-            DistanceConstraint* stick = new DistanceConstraint(&(_points[i]), &(_points[parentIndex]));
-            _constraints.push_back(stick);
-        }
-    }
-}
-
-/// Delete all data.
-void RagDoll::clear() {
-    int numConstraints = _constraints.size();
-    for (int i = 0; i < numConstraints; ++i) {
-        delete _constraints[i];
-    }
-    _constraints.clear();
-    _points.clear();
-}
-
-float RagDoll::slaveToSkeleton(const QVector<JointState>& states, float fraction) {
-    const int numStates = states.size();
-    assert(numStates == _points.size());
-    fraction = glm::clamp(fraction, 0.0f, 1.0f);
-    float maxDistance = 0.0f;
-    for (int i = 0; i < numStates; ++i) {
-        glm::vec3 oldPoint = _points[i];
-        _points[i] = (1.0f - fraction) * _points[i] + fraction * states[i].getPosition();
-        maxDistance = glm::max(maxDistance, glm::distance(oldPoint, _points[i]));
-    }
-    return maxDistance;
-}
-
-float RagDoll::enforceConstraints() {
-    float maxDistance = 0.0f;
-    const int numConstraints = _constraints.size();
-    for (int i = 0; i < numConstraints; ++i) {
-        DistanceConstraint* c = static_cast<DistanceConstraint*>(_constraints[i]);
-        //maxDistance = glm::max(maxDistance, _constraints[i]->enforce());
-        maxDistance = glm::max(maxDistance, c->enforce());
-    }
-    return maxDistance;
-}
-
-void RagDoll::updateShapes(const QVector<Shape*>& shapes, const glm::quat& rotation, const glm::vec3& translation) const {
-    int numShapes = shapes.size();
-    int numConstraints = _constraints.size();
-    for (int i = 0; i < numShapes && i < numConstraints; ++i) {
-        _constraints[i]->updateProxyShape(shapes[i], rotation, translation);
-    }
-}
diff --git a/interface/src/renderer/RagDoll.h b/interface/src/renderer/RagDoll.h
deleted file mode 100644
index 60e242d19b..0000000000
--- a/interface/src/renderer/RagDoll.h
+++ /dev/null
@@ -1,95 +0,0 @@
-//
-//  RagDoll.h
-//  interface/src/avatar
-//
-//  Created by Andrew Meadows 2014.05.30
-//  Copyright 2014 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
-//
-
-#ifndef hifi_RagDoll_h
-#define hifi_RagDoll_h
-
-#include "renderer/Model.h"
-
-class Shape;
-
-class Constraint {
-public:
-    Constraint() {}
-    virtual ~Constraint() {}
-
-    /// Enforce contraint by moving relevant points.
-    /// \return max distance of point movement
-    virtual float enforce() = 0;
-
-    /// \param shape pointer to shape that will be this Constraint's collision proxy
-    /// \param rotation rotation into shape's collision frame
-    /// \param translation translation into shape's collision frame
-    /// Moves the shape such that it will collide at this constraint's position
-    virtual void updateProxyShape(Shape* shape, const glm::quat& rotation, const glm::vec3& translation) const {}
-
-protected:
-    int _type;
-};
-
-class FixedConstraint : public Constraint {
-public:
-    FixedConstraint(glm::vec3* point, const glm::vec3& anchor);
-    float enforce();
-    void setPoint(glm::vec3* point);
-    void setAnchor(const glm::vec3& anchor);
-private:
-    glm::vec3* _point;
-    glm::vec3 _anchor;
-};
-
-class DistanceConstraint : public Constraint {
-public:
-    DistanceConstraint(glm::vec3* startPoint, glm::vec3* endPoint);
-    DistanceConstraint(const DistanceConstraint& other);
-    float enforce();
-    void setDistance(float distance);
-    void updateProxyShape(Shape* shape, const glm::quat& rotation, const glm::vec3& translation) const;
-private:
-    float _distance;
-    glm::vec3* _points[2];
-};
-
-class RagDoll {
-public:
-
-    RagDoll();
-    virtual ~RagDoll();
-    
-    /// Create points and constraints based on topology of collection of joints
-    /// \param joints list of connected joint states
-    void init(const QVector<JointState>& states);
-
-    /// Delete all data.
-    void clear();
-
-    /// \param states list of joint states
-    /// \param fraction range from 0.0 (no movement) to 1.0 (use joint locations)
-    /// \return max distance of point movement
-    float slaveToSkeleton(const QVector<JointState>& states, float fraction);
-
-    /// Enforce contraints.
-    /// \return max distance of point movement
-    float enforceConstraints();
-
-    const QVector<glm::vec3>& getPoints() const { return _points; }
-
-    /// \param shapes list of shapes to be updated with new positions
-    /// \param rotation rotation into shapes' collision frame
-    /// \param translation translation into shapes' collision frame
-    void updateShapes(const QVector<Shape*>& shapes, const glm::quat& rotation, const glm::vec3& translation) const;
-
-private:
-    QVector<Constraint*> _constraints;
-    QVector<glm::vec3> _points;
-};
-
-#endif // hifi_RagDoll_h
diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp
index 0588ca70d2..55a67ce854 100644
--- a/interface/src/renderer/TextureCache.cpp
+++ b/interface/src/renderer/TextureCache.cpp
@@ -28,10 +28,12 @@ TextureCache::TextureCache() :
     _permutationNormalTextureID(0),
     _whiteTextureID(0),
     _blueTextureID(0),
+    _primaryDepthTextureID(0),
     _primaryFramebufferObject(NULL),
     _secondaryFramebufferObject(NULL),
     _tertiaryFramebufferObject(NULL),
-    _shadowFramebufferObject(NULL)
+    _shadowFramebufferObject(NULL),
+    _frameBufferSize(100, 100)
 {
 }
 
@@ -46,9 +48,41 @@ TextureCache::~TextureCache() {
         glDeleteTextures(1, &_primaryDepthTextureID);
     }
     
-    delete _primaryFramebufferObject;
-    delete _secondaryFramebufferObject;
-    delete _tertiaryFramebufferObject;
+    if (_primaryFramebufferObject) {
+        delete _primaryFramebufferObject;
+    }
+
+    if (_secondaryFramebufferObject) {
+        delete _secondaryFramebufferObject;
+    }
+
+    if (_tertiaryFramebufferObject) {
+        delete _tertiaryFramebufferObject;
+    }
+}
+
+void TextureCache::setFrameBufferSize(QSize frameBufferSize) {
+    //If the size changed, we need to delete our FBOs
+    if (_frameBufferSize != frameBufferSize) {
+        _frameBufferSize = frameBufferSize;
+
+        if (_primaryFramebufferObject) {
+            delete _primaryFramebufferObject;
+            _primaryFramebufferObject = NULL;
+            glDeleteTextures(1, &_primaryDepthTextureID);
+            _primaryDepthTextureID = 0;
+        }
+
+        if (_secondaryFramebufferObject) {
+            delete _secondaryFramebufferObject;
+            _secondaryFramebufferObject = NULL;
+        }
+
+        if (_tertiaryFramebufferObject) {
+            delete _tertiaryFramebufferObject;
+            _tertiaryFramebufferObject = NULL;
+        }
+    }
 }
 
 GLuint TextureCache::getPermutationNormalTextureID() {
@@ -131,13 +165,14 @@ QSharedPointer<NetworkTexture> TextureCache::getTexture(const QUrl& url, bool no
 }
 
 QOpenGLFramebufferObject* TextureCache::getPrimaryFramebufferObject() {
+
     if (!_primaryFramebufferObject) {
         _primaryFramebufferObject = createFramebufferObject();
-        
+       
         glGenTextures(1, &_primaryDepthTextureID);
         glBindTexture(GL_TEXTURE_2D, _primaryDepthTextureID);
-        QSize size = Application::getInstance()->getGLWidget()->size();
-        glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, size.width(), size.height(),
+
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, _frameBufferSize.width(), _frameBufferSize.height(),
             0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
@@ -230,7 +265,7 @@ QSharedPointer<Resource> TextureCache::createResource(const QUrl& url,
 }
 
 QOpenGLFramebufferObject* TextureCache::createFramebufferObject() {
-    QOpenGLFramebufferObject* fbo = new QOpenGLFramebufferObject(Application::getInstance()->getGLWidget()->size());
+    QOpenGLFramebufferObject* fbo = new QOpenGLFramebufferObject(_frameBufferSize);
     Application::getInstance()->getGLWidget()->installEventFilter(this);
     
     glBindTexture(GL_TEXTURE_2D, fbo->texture());
diff --git a/interface/src/renderer/TextureCache.h b/interface/src/renderer/TextureCache.h
index f4444b6dfc..248a451e3a 100644
--- a/interface/src/renderer/TextureCache.h
+++ b/interface/src/renderer/TextureCache.h
@@ -32,6 +32,9 @@ public:
     TextureCache();
     virtual ~TextureCache();
     
+    /// Sets the desired texture resolution for the framebuffer objects. 
+    void setFrameBufferSize(QSize frameBufferSize);
+
     /// Returns the ID of the permutation/normal texture used for Perlin noise shader programs.  This texture
     /// has two lines: the first, a set of random numbers in [0, 255] to be used as permutation offsets, and
     /// the second, a set of random unit vectors to be used as noise gradients.
@@ -94,6 +97,8 @@ private:
     
     QOpenGLFramebufferObject* _shadowFramebufferObject;
     GLuint _shadowDepthTextureID;
+
+    QSize _frameBufferSize;
 };
 
 /// A simple object wrapper for an OpenGL texture.
diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp
index 9be556cf62..77e8986297 100644
--- a/interface/src/ui/ApplicationOverlay.cpp
+++ b/interface/src/ui/ApplicationOverlay.cpp
@@ -39,9 +39,10 @@ inline float min(float a, float b) {
 ApplicationOverlay::ApplicationOverlay() : 
     _framebufferObject(NULL),
     _textureFov(DEFAULT_OCULUS_UI_ANGULAR_SIZE * RADIANS_PER_DEGREE),
-    _crosshairTexture(0),
     _alpha(1.0f),
-    _active(true) {
+    _active(true),
+    _crosshairTexture(0)
+{
 
     memset(_reticleActive, 0, sizeof(_reticleActive));
     memset(_magActive, 0, sizeof(_reticleActive));
@@ -375,7 +376,7 @@ void ApplicationOverlay::renderControllerPointers() {
             //then disable it.
 
             const int MAX_BUTTON_PRESS_TIME = 250 * MSECS_TO_USECS;
-            if (usecTimestampNow() - pressedTime[index] < MAX_BUTTON_PRESS_TIME) {
+            if (usecTimestampNow() < pressedTime[index] + MAX_BUTTON_PRESS_TIME) {
                 _magActive[index] = !stateWhenPressed[index];
             }
         }
diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp
index fde77334f4..23bf2eafc4 100644
--- a/interface/src/ui/ChatWindow.cpp
+++ b/interface/src/ui/ChatWindow.cpp
@@ -30,6 +30,9 @@
 
 const int NUM_MESSAGES_TO_TIME_STAMP = 20;
 
+const float OPACITY_ACTIVE = 1.0;
+const float OPACITY_INACTIVE = 0.8;
+
 const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?)|(?:hifi))://\\S+)");
 const QRegularExpression regexHifiLinks("([#@]\\S+)");
 const QString mentionSoundsPath("/mention-sounds/");
@@ -108,7 +111,7 @@ ChatWindow::~ChatWindow() {
 
 void ChatWindow::keyPressEvent(QKeyEvent* event) {
     if (event->key() == Qt::Key_Escape) {
-        hide();
+        Application::getInstance()->getWindow()->activateWindow();
     } else {
         FramelessDialog::keyPressEvent(event);
     }
@@ -178,7 +181,7 @@ void ChatWindow::addTimeStamp() {
         QLabel* timeLabel = new QLabel(timeString);
         timeLabel->setStyleSheet("color: #333333;"
                                  "background-color: white;"
-                                 "font-size: 14pt;"
+                                 "font-size: 14px;"
                                  "padding: 4px;");
         timeLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
         timeLabel->setAlignment(Qt::AlignLeft);
@@ -284,7 +287,7 @@ void ChatWindow::participantsChanged() {
                                  "padding-bottom: 2px;"
                                  "padding-left: 2px;"
                                  "border: 1px solid palette(shadow);"
-                                 "font-size: 14pt;"
+                                 "font-size: 14px;"
                                  "font-weight: bold");
         userLabel->setProperty("user", participantName);
         userLabel->setCursor(Qt::PointingHandCursor);
@@ -320,7 +323,7 @@ void ChatWindow::messageReceived(const QXmppMessage& message) {
                                "padding-right: 20px;"
                                "margin: 0px;"
                                "color: #333333;"
-                               "font-size: 14pt;"
+                               "font-size: 14px;"
                                "background-color: rgba(0, 0, 0, 0%);"
                                "border: 0; }"
                                "QMenu{ border: 2px outset gray; }");
@@ -383,3 +386,12 @@ void ChatWindow::scrollToBottom() {
     QScrollBar* verticalScrollBar = ui->messagesScrollArea->verticalScrollBar();
     verticalScrollBar->setValue(verticalScrollBar->maximum());
 }
+
+bool ChatWindow::event(QEvent* event) {
+    if (event->type() == QEvent::WindowActivate) {
+        setWindowOpacity(OPACITY_ACTIVE);
+    } else if (event->type() == QEvent::WindowDeactivate) {
+        setWindowOpacity(OPACITY_INACTIVE);
+    }
+    return FramelessDialog::event(event);
+}
diff --git a/interface/src/ui/ChatWindow.h b/interface/src/ui/ChatWindow.h
index 1e0f533e9e..652dcb5b08 100644
--- a/interface/src/ui/ChatWindow.h
+++ b/interface/src/ui/ChatWindow.h
@@ -50,6 +50,7 @@ protected:
 
     virtual void keyPressEvent(QKeyEvent *event);
     virtual void showEvent(QShowEvent* event);
+    virtual bool event(QEvent* event);
 
 private:
 #ifdef HAVE_QXMPP
diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp
index a42c84470b..1f0c2498c5 100644
--- a/interface/src/ui/MetavoxelEditor.cpp
+++ b/interface/src/ui/MetavoxelEditor.cpp
@@ -29,6 +29,7 @@
 #include <QVBoxLayout>
 
 #include <AttributeRegistry.h>
+#include <MetavoxelMessages.h>
 
 #include "Application.h"
 #include "MetavoxelEditor.h"
@@ -771,7 +772,7 @@ int VoxelizationVisitor::visit(MetavoxelInfo& info) {
         }
         return DEFAULT_ORDER;
     }
-    QRgb closestColor;
+    QRgb closestColor = QRgb();
     float closestDistance = FLT_MAX;
     for (unsigned int i = 0; i < sizeof(DIRECTION_ROTATIONS) / sizeof(DIRECTION_ROTATIONS[0]); i++) {
         glm::vec3 rotated = DIRECTION_ROTATIONS[i] * center;
diff --git a/interface/src/ui/ModelsBrowser.cpp b/interface/src/ui/ModelsBrowser.cpp
index 4296a096a0..203c54d97a 100644
--- a/interface/src/ui/ModelsBrowser.cpp
+++ b/interface/src/ui/ModelsBrowser.cpp
@@ -89,6 +89,12 @@ ModelsBrowser::ModelsBrowser(ModelType modelsType, QWidget* parent) :
     _view.setEditTriggers(QAbstractItemView::NoEditTriggers);
     _view.setRootIsDecorated(false);
     _view.setModel(_handler->getModel());
+    _view.blockSignals(true);
+
+    // Initialize the search bar
+    _searchBar = new QLineEdit;
+    _searchBar->setDisabled(true);
+    connect(_handler, SIGNAL(doneDownloading()), SLOT(enableSearchBar()));
 }
 
 void ModelsBrowser::applyFilter(const QString &filter) {
@@ -130,6 +136,11 @@ void ModelsBrowser::resizeView() {
     }
 }
 
+void ModelsBrowser::enableSearchBar() {
+    _view.blockSignals(false);
+    _searchBar->setEnabled(true);
+}
+
 void ModelsBrowser::browse() {
     QDialog dialog;
     dialog.setWindowTitle("Browse models");
@@ -138,12 +149,10 @@ void ModelsBrowser::browse() {
     QGridLayout* layout = new QGridLayout(&dialog);
     dialog.setLayout(layout);
     
-    QLineEdit* searchBar = new QLineEdit(&dialog);
-    layout->addWidget(searchBar, 0, 0);
-    
+    layout->addWidget(_searchBar, 0, 0);
     layout->addWidget(&_view, 1, 0);
     dialog.connect(&_view, SIGNAL(doubleClicked(const QModelIndex&)), SLOT(accept()));
-    connect(searchBar, SIGNAL(textChanged(const QString&)), SLOT(applyFilter(const QString&)));
+    connect(_searchBar, SIGNAL(textChanged(const QString&)), SLOT(applyFilter(const QString&)));
     
     QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
     layout->addWidget(buttons, 2, 0);
diff --git a/interface/src/ui/ModelsBrowser.h b/interface/src/ui/ModelsBrowser.h
index ff273a45bc..3e832c9dbe 100644
--- a/interface/src/ui/ModelsBrowser.h
+++ b/interface/src/ui/ModelsBrowser.h
@@ -74,9 +74,11 @@ public slots:
 private slots:
     void applyFilter(const QString& filter);
     void resizeView();
+    void enableSearchBar();
     
 private:
     ModelHandler* _handler;
+    QLineEdit* _searchBar;
     QTreeView _view;
 };
 
diff --git a/interface/src/ui/OAuthWebViewHandler.cpp b/interface/src/ui/OAuthWebViewHandler.cpp
index 5b4431bd0f..8ec415584d 100644
--- a/interface/src/ui/OAuthWebViewHandler.cpp
+++ b/interface/src/ui/OAuthWebViewHandler.cpp
@@ -109,6 +109,8 @@ void OAuthWebViewHandler::displayWebviewForAuthorizationURL(const QUrl& authoriz
         
         connect(_activeWebView->page()->networkAccessManager(), &QNetworkAccessManager::sslErrors,
                 this, &OAuthWebViewHandler::handleSSLErrors);
+        connect(_activeWebView->page()->networkAccessManager(), &QNetworkAccessManager::finished,
+                this, &OAuthWebViewHandler::handleReplyFinished);
         connect(_activeWebView.data(), &QWebView::loadFinished, this, &OAuthWebViewHandler::handleLoadFinished);
         
         // connect to the destroyed signal so after the web view closes we can start a timer
@@ -132,6 +134,14 @@ void OAuthWebViewHandler::handleLoadFinished(bool success) {
         NodeList::getInstance()->setSessionUUID(QUuid(authQuery.queryItemValue(AUTH_STATE_QUERY_KEY)));
         
         _activeWebView->close();
+        _activeWebView = NULL;
+    }
+}
+
+void OAuthWebViewHandler::handleReplyFinished(QNetworkReply* reply) {
+    if (_activeWebView && reply->error() != QNetworkReply::NoError) {
+        qDebug() << "Error loading" << reply->url() << "-" << reply->errorString();
+        _activeWebView->close();
     }
 }
 
@@ -148,6 +158,7 @@ void OAuthWebViewHandler::handleURLChanged(const QUrl& url) {
         _activeWebView->show();
     } else if (url.toString() == DEFAULT_NODE_AUTH_URL.toString() + "/login") {
         // this is a login request - we're going to close the webview and signal the AccountManager that we need a login
+        qDebug() << "data-server replied with login request. Signalling that login is required to proceed with OAuth.";
         _activeWebView->close();
         AccountManager::getInstance().checkAndSignalForAccessToken();        
     }
diff --git a/interface/src/ui/OAuthWebViewHandler.h b/interface/src/ui/OAuthWebViewHandler.h
index 8f0c01c90d..1a95f17dfd 100644
--- a/interface/src/ui/OAuthWebViewHandler.h
+++ b/interface/src/ui/OAuthWebViewHandler.h
@@ -31,6 +31,7 @@ public slots:
 private slots:
     void handleSSLErrors(QNetworkReply* networkReply, const QList<QSslError>& errorList);
     void handleLoadFinished(bool success);
+    void handleReplyFinished(QNetworkReply* reply);
     void handleWebViewDestroyed(QObject* destroyedObject);
     void handleURLChanged(const QUrl& url);
 private:
diff --git a/interface/src/ui/OctreeStatsDialog.cpp b/interface/src/ui/OctreeStatsDialog.cpp
index 3296d8ccb2..afa799815f 100644
--- a/interface/src/ui/OctreeStatsDialog.cpp
+++ b/interface/src/ui/OctreeStatsDialog.cpp
@@ -365,13 +365,14 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser
                             QString incomingPacketsString = locale.toString((uint)stats.getIncomingPackets());
                             QString incomingBytesString = locale.toString((uint)stats.getIncomingBytes());
                             QString incomingWastedBytesString = locale.toString((uint)stats.getIncomingWastedBytes());
-                            QString incomingOutOfOrderString = locale.toString((uint)stats.getIncomingOutOfOrder());
-                            QString incomingLateString = locale.toString((uint)stats.getIncomingLate());
-                            QString incomingReallyLateString = locale.toString((uint)stats.getIncomingReallyLate());
-                            QString incomingEarlyString = locale.toString((uint)stats.getIncomingEarly());
-                            QString incomingLikelyLostString = locale.toString((uint)stats.getIncomingLikelyLost());
-                            QString incomingRecovered = locale.toString((uint)stats.getIncomingRecovered());
-                            QString incomingDuplicateString = locale.toString((uint)stats.getIncomingPossibleDuplicate());
+                            const SequenceNumberStats& seqStats = stats.getIncomingOctreeSequenceNumberStats();
+                            QString incomingOutOfOrderString = locale.toString((uint)seqStats.getNumOutOfOrder());
+                            QString incomingLateString = locale.toString((uint)seqStats.getNumLate());
+                            QString incomingUnreasonableString = locale.toString((uint)seqStats.getNumUnreasonable());
+                            QString incomingEarlyString = locale.toString((uint)seqStats.getNumEarly());
+                            QString incomingLikelyLostString = locale.toString((uint)seqStats.getNumLost());
+                            QString incomingRecovered = locale.toString((uint)seqStats.getNumRecovered());
+                            QString incomingDuplicateString = locale.toString((uint)seqStats.getNumDuplicate());
                             
                             int clockSkewInMS = node->getClockSkewUsec() / (int)USECS_PER_MSEC;
                             QString incomingFlightTimeString = locale.toString((int)stats.getIncomingFlightTimeAverage());
@@ -385,7 +386,7 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser
                             serverDetails << "<br/>" << " Out of Order: " << qPrintable(incomingOutOfOrderString) <<
                                 "/ Early: " << qPrintable(incomingEarlyString) <<
                                 "/ Late: " << qPrintable(incomingLateString) <<
-                                "/ Really Late: " << qPrintable(incomingReallyLateString) <<
+                                "/ Unreasonable: " << qPrintable(incomingUnreasonableString) <<
                                 "/ Duplicate: " << qPrintable(incomingDuplicateString);
                             
                             serverDetails << "<br/>" <<
diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp
index 5e6c6984eb..9c89826cb9 100644
--- a/interface/src/ui/PreferencesDialog.cpp
+++ b/interface/src/ui/PreferencesDialog.cpp
@@ -12,8 +12,9 @@
 
 #include "Application.h"
 #include "Menu.h"
-#include "PreferencesDialog.h"
 #include "ModelsBrowser.h"
+#include "PreferencesDialog.h"
+#include "UserActivityLogger.h"
 
 const int SCROLL_PANEL_BOTTOM_MARGIN = 30;
 const int OK_BUTTON_RIGHT_MARGIN = 30;
@@ -29,6 +30,7 @@ PreferencesDialog::PreferencesDialog(QWidget* parent, Qt::WindowFlags flags) : F
     connect(ui.buttonBrowseHead, &QPushButton::clicked, this, &PreferencesDialog::openHeadModelBrowser);
     connect(ui.buttonBrowseBody, &QPushButton::clicked, this, &PreferencesDialog::openBodyModelBrowser);
     connect(ui.buttonBrowseLocation, &QPushButton::clicked, this, &PreferencesDialog::openSnapshotLocationBrowser);
+    connect(ui.buttonBrowseScriptsLocation, &QPushButton::clicked, this, &PreferencesDialog::openScriptsLocationBrowser);
     connect(ui.buttonReloadDefaultScripts, &QPushButton::clicked,
             Application::getInstance(), &Application::loadDefaultScripts);
 }
@@ -72,13 +74,32 @@ void PreferencesDialog::openBodyModelBrowser() {
 
 void PreferencesDialog::openSnapshotLocationBrowser() {
     setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint);
+    show();
+
     QString dir = QFileDialog::getExistingDirectory(this, tr("Snapshots Location"),
                                                     QStandardPaths::writableLocation(QStandardPaths::DesktopLocation),
                                                     QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
     if (!dir.isNull() && !dir.isEmpty()) {
         ui.snapshotLocationEdit->setText(dir);
     }
+
     setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
+    show();
+}
+
+void PreferencesDialog::openScriptsLocationBrowser() {
+    setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint);
+    show();
+
+    QString dir = QFileDialog::getExistingDirectory(this, tr("Scripts Location"),
+                                                    ui.scriptsLocationEdit->text(),
+                                                    QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
+    if (!dir.isNull() && !dir.isEmpty()) {
+        ui.scriptsLocationEdit->setText(dir);
+    }
+
+    setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
+    show();
 }
 
 void PreferencesDialog::resizeEvent(QResizeEvent *resizeEvent) {
@@ -118,6 +139,8 @@ void PreferencesDialog::loadPreferences() {
 
     ui.snapshotLocationEdit->setText(menuInstance->getSnapshotsLocation());
 
+    ui.scriptsLocationEdit->setText(menuInstance->getScriptsLocation());
+
     ui.pupilDilationSlider->setValue(myAvatar->getHead()->getPupilDilation() *
                                      ui.pupilDilationSlider->maximum());
     
@@ -154,6 +177,7 @@ void PreferencesDialog::savePreferences() {
     QString displayNameStr(ui.displayNameEdit->text());
     if (displayNameStr != _displayNameString) {
         myAvatar->setDisplayName(displayNameStr);
+        UserActivityLogger::getInstance().changedDisplayName(displayNameStr);
         shouldDispatchIdentityPacket = true;
     }
     
@@ -161,6 +185,7 @@ void PreferencesDialog::savePreferences() {
     if (faceModelURL.toString() != _faceURLString) {
         // change the faceModelURL in the profile, it will also update this user's BlendFace
         myAvatar->setFaceModelURL(faceModelURL);
+        UserActivityLogger::getInstance().changedModel("head", faceModelURL.toString());
         shouldDispatchIdentityPacket = true;
     }
 
@@ -168,6 +193,7 @@ void PreferencesDialog::savePreferences() {
     if (skeletonModelURL.toString() != _skeletonURLString) {
         // change the skeletonModelURL in the profile, it will also update this user's Body
         myAvatar->setSkeletonModelURL(skeletonModelURL);
+        UserActivityLogger::getInstance().changedModel("skeleton", skeletonModelURL.toString());
         shouldDispatchIdentityPacket = true;
     }
     
@@ -180,6 +206,10 @@ void PreferencesDialog::savePreferences() {
         Menu::getInstance()->setSnapshotsLocation(ui.snapshotLocationEdit->text());
     }
 
+    if (!ui.scriptsLocationEdit->text().isEmpty() && QDir(ui.scriptsLocationEdit->text()).exists()) {
+        Menu::getInstance()->setScriptsLocation(ui.scriptsLocationEdit->text());
+    }
+
     myAvatar->getHead()->setPupilDilation(ui.pupilDilationSlider->value() / (float)ui.pupilDilationSlider->maximum());
     myAvatar->setLeanScale(ui.leanScaleSpin->value());
     myAvatar->setClampedTargetScale(ui.avatarScaleSpin->value());
diff --git a/interface/src/ui/PreferencesDialog.h b/interface/src/ui/PreferencesDialog.h
index c52986cc5c..0573304eda 100644
--- a/interface/src/ui/PreferencesDialog.h
+++ b/interface/src/ui/PreferencesDialog.h
@@ -42,6 +42,7 @@ private slots:
     void setHeadUrl(QString modelUrl);
     void setSkeletonUrl(QString modelUrl);
     void openSnapshotLocationBrowser();
+    void openScriptsLocationBrowser();
     
 };
 
diff --git a/interface/src/ui/RunningScriptsWidget.cpp b/interface/src/ui/RunningScriptsWidget.cpp
index 9bef454829..8a7ebcbfd4 100644
--- a/interface/src/ui/RunningScriptsWidget.cpp
+++ b/interface/src/ui/RunningScriptsWidget.cpp
@@ -3,6 +3,7 @@
 //  interface/src/ui
 //
 //  Created by Mohammed Nafees on 03/28/2014.
+//  Updated by Ryan Huffman on 05/13/2014.
 //  Copyright 2014 High Fidelity, Inc.
 //
 //  Distributed under the Apache License, Version 2.0.
@@ -12,18 +13,27 @@
 #include "ui_runningScriptsWidget.h"
 #include "RunningScriptsWidget.h"
 
+#include <QAbstractProxyModel>
 #include <QFileInfo>
 #include <QKeyEvent>
 #include <QPainter>
 #include <QTableWidgetItem>
 
 #include "Application.h"
+#include "Menu.h"
+#include "ScriptsModel.h"
+
 
 RunningScriptsWidget::RunningScriptsWidget(QWidget* parent) :
     FramelessDialog(parent, 0, POSITION_LEFT),
-    ui(new Ui::RunningScriptsWidget) {
+    ui(new Ui::RunningScriptsWidget),
+    _signalMapper(this),
+    _proxyModel(this),
+    _scriptsModel(this) {
     ui->setupUi(this);
 
+    setAttribute(Qt::WA_DeleteOnClose, false);
+
     setAllowResize(false);
 
     ui->hideWidgetButton->setIcon(QIcon(Application::resourcesPath() + "images/close.svg"));
@@ -31,17 +41,24 @@ RunningScriptsWidget::RunningScriptsWidget(QWidget* parent) :
     ui->stopAllButton->setIcon(QIcon(Application::resourcesPath() + "images/stop.svg"));
     ui->loadScriptButton->setIcon(QIcon(Application::resourcesPath() + "images/plus-white.svg"));
 
-    _runningScriptsTable = new ScriptsTableWidget(ui->runningScriptsTableWidget);
-    _runningScriptsTable->setColumnCount(2);
-    _runningScriptsTable->setColumnWidth(0, 245);
-    _runningScriptsTable->setColumnWidth(1, 22);
-    connect(_runningScriptsTable, &QTableWidget::cellClicked, this, &RunningScriptsWidget::stopScript);
+    ui->recentlyLoadedScriptsArea->hide();
+
+    ui->filterLineEdit->installEventFilter(this);
+
+    connect(&_proxyModel, &QSortFilterProxyModel::modelReset,
+            this, &RunningScriptsWidget::selectFirstInList);
+
+    _proxyModel.setSourceModel(&_scriptsModel);
+    _proxyModel.sort(0, Qt::AscendingOrder);
+    _proxyModel.setDynamicSortFilter(true);
+    ui->scriptListView->setModel(&_proxyModel);
+
+    connect(ui->filterLineEdit, &QLineEdit::textChanged, this, &RunningScriptsWidget::updateFileFilter);
+    connect(ui->scriptListView, &QListView::doubleClicked, this, &RunningScriptsWidget::loadScriptFromList);
 
     _recentlyLoadedScriptsTable = new ScriptsTableWidget(ui->recentlyLoadedScriptsTableWidget);
     _recentlyLoadedScriptsTable->setColumnCount(1);
     _recentlyLoadedScriptsTable->setColumnWidth(0, 265);
-    connect(_recentlyLoadedScriptsTable, &QTableWidget::cellClicked,
-            this, &RunningScriptsWidget::loadScript);
 
     connect(ui->hideWidgetButton, &QPushButton::clicked,
             Application::getInstance(), &Application::toggleRunningScriptsWidget);
@@ -51,59 +68,121 @@ RunningScriptsWidget::RunningScriptsWidget(QWidget* parent) :
             this, &RunningScriptsWidget::allScriptsStopped);
     connect(ui->loadScriptButton, &QPushButton::clicked,
             Application::getInstance(), &Application::loadDialog);
+    connect(&_signalMapper, SIGNAL(mapped(QString)), Application::getInstance(), SLOT(stopScript(const QString&)));
 }
 
 RunningScriptsWidget::~RunningScriptsWidget() {
     delete ui;
 }
 
+void RunningScriptsWidget::updateFileFilter(const QString& filter) {
+    QRegExp regex("^.*" + QRegExp::escape(filter) + ".*$", Qt::CaseInsensitive);
+    _proxyModel.setFilterRegExp(regex);
+    selectFirstInList();
+}
+
+void RunningScriptsWidget::loadScriptFromList(const QModelIndex& index) {
+    QVariant scriptFile = _proxyModel.data(index, ScriptsModel::ScriptPath);
+    Application::getInstance()->loadScript(scriptFile.toString(), false, false);
+}
+
+void RunningScriptsWidget::loadSelectedScript() {
+    QModelIndex selectedIndex = ui->scriptListView->currentIndex();
+    if (selectedIndex.isValid()) {
+        loadScriptFromList(selectedIndex);
+    }
+}
+
 void RunningScriptsWidget::setBoundary(const QRect& rect) {
     _boundary = rect;
 }
 
 void RunningScriptsWidget::setRunningScripts(const QStringList& list) {
-    _runningScriptsTable->setRowCount(list.size());
+    setUpdatesEnabled(false);
+    QLayoutItem* widget;
+    while ((widget = ui->scrollAreaWidgetContents->layout()->takeAt(0)) != NULL) {
+        delete widget->widget();
+        delete widget;
+    }
+    const int CLOSE_ICON_HEIGHT = 12;
+    for (int i = 0; i < list.size(); i++) {
+        QWidget* row = new QWidget(ui->scrollAreaWidgetContents);
+        row->setLayout(new QHBoxLayout(row));
+
+        QUrl url = QUrl(list.at(i));
+        QLabel* name = new QLabel(url.fileName(), row);
+        QPushButton* closeButton = new QPushButton(row);
+        closeButton->setFlat(true);
+        closeButton->setIcon(
+            QIcon(QPixmap(Application::resourcesPath() + "images/kill-script.svg").scaledToHeight(CLOSE_ICON_HEIGHT)));
+        closeButton->setSizePolicy(QSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred));
+        closeButton->setStyleSheet("border: 0;");
+        closeButton->setCursor(Qt::PointingHandCursor);
+
+        connect(closeButton, SIGNAL(clicked()), &_signalMapper, SLOT(map()));
+        _signalMapper.setMapping(closeButton, url.toString());
+
+        row->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
+
+        row->layout()->setContentsMargins(4, 4, 4, 4);
+        row->layout()->setSpacing(0);
+
+        row->layout()->addWidget(name);
+        row->layout()->addWidget(closeButton);
+
+        row->setToolTip(url.toString());
+
+        QFrame* line = new QFrame(row);
+        line->setFrameShape(QFrame::HLine);
+        line->setStyleSheet("color: #E1E1E1; margin-left: 6px; margin-right: 6px;");
+
+        ui->scrollAreaWidgetContents->layout()->addWidget(row);
+        ui->scrollAreaWidgetContents->layout()->addWidget(line);
+    }
+
 
     ui->noRunningScriptsLabel->setVisible(list.isEmpty());
-    ui->currentlyRunningLabel->setVisible(!list.isEmpty());
-    ui->runningScriptsTableWidget->setVisible(!list.isEmpty());
     ui->reloadAllButton->setVisible(!list.isEmpty());
     ui->stopAllButton->setVisible(!list.isEmpty());
 
-    const int CLOSE_ICON_HEIGHT = 12;
+    ui->scrollAreaWidgetContents->updateGeometry();
+    setUpdatesEnabled(true);
+    Application::processEvents();
+    repaint();
+}
 
-    for (int i = 0; i < list.size(); ++i) {
-        QTableWidgetItem *scriptName = new QTableWidgetItem;
-        scriptName->setText(QFileInfo(list.at(i)).fileName());
-        scriptName->setToolTip(list.at(i));
-        scriptName->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
-        QTableWidgetItem *closeIcon = new QTableWidgetItem;
-        closeIcon->setIcon(QIcon(QPixmap(Application::resourcesPath() + "images/kill-script.svg").scaledToHeight(CLOSE_ICON_HEIGHT)));
-
-        _runningScriptsTable->setItem(i, 0, scriptName);
-        _runningScriptsTable->setItem(i, 1, closeIcon);
+void RunningScriptsWidget::showEvent(QShowEvent* event) {
+    if (!event->spontaneous()) {
+        ui->filterLineEdit->setFocus();
     }
 
-    const int RUNNING_SCRIPTS_TABLE_LEFT_MARGIN = 12;
-    const int RECENTLY_LOADED_TOP_MARGIN = 61;
-    const int RECENTLY_LOADED_LABEL_TOP_MARGIN = 19;
+    FramelessDialog::showEvent(event);
+}
 
-    int y = ui->runningScriptsTableWidget->y() + RUNNING_SCRIPTS_TABLE_LEFT_MARGIN;
-    for (int i = 0; i < _runningScriptsTable->rowCount(); ++i) {
-        y += _runningScriptsTable->rowHeight(i);
+void RunningScriptsWidget::selectFirstInList() {
+    if (_proxyModel.rowCount() > 0) {
+        ui->scriptListView->setCurrentIndex(_proxyModel.index(0, 0));
+    }
+}
+
+bool RunningScriptsWidget::eventFilter(QObject* sender, QEvent* event) {
+    if (sender == ui->filterLineEdit) {
+        if (event->type() != QEvent::KeyPress) {
+            return false;
+        }
+        QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
+        if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) {
+            QModelIndex selectedIndex = ui->scriptListView->currentIndex();
+            if (selectedIndex.isValid()) {
+                loadScriptFromList(selectedIndex);
+            }
+            event->accept();
+            return true;
+        }
+        return false;
     }
 
-    ui->runningScriptsTableWidget->resize(ui->runningScriptsTableWidget->width(), y - RUNNING_SCRIPTS_TABLE_LEFT_MARGIN);
-    _runningScriptsTable->resize(_runningScriptsTable->width(), y - RUNNING_SCRIPTS_TABLE_LEFT_MARGIN);
-    ui->reloadAllButton->move(ui->reloadAllButton->x(), y);
-    ui->stopAllButton->move(ui->stopAllButton->x(), y);
-    ui->recentlyLoadedLabel->move(ui->recentlyLoadedLabel->x(),
-                                  ui->stopAllButton->y() + ui->stopAllButton->height() + RECENTLY_LOADED_TOP_MARGIN);
-    ui->recentlyLoadedScriptsTableWidget->move(ui->recentlyLoadedScriptsTableWidget->x(),
-                                               ui->recentlyLoadedLabel->y() + RECENTLY_LOADED_LABEL_TOP_MARGIN);
-
-
-    createRecentlyLoadedScriptsTable();
+    return FramelessDialog::eventFilter(sender, event);
 }
 
 void RunningScriptsWidget::keyPressEvent(QKeyEvent *keyEvent) {
@@ -114,79 +193,10 @@ void RunningScriptsWidget::keyPressEvent(QKeyEvent *keyEvent) {
     }
 }
 
-void RunningScriptsWidget::paintEvent(QPaintEvent* event) {
-    QPainter painter(this);
-    painter.setPen(QColor::fromRgb(225, 225, 225)); // #e1e1e1
-
-    if (ui->currentlyRunningLabel->isVisible()) {
-        // line below the 'Currently Running' label
-        painter.drawLine(36, ui->currentlyRunningLabel->y() + ui->currentlyRunningLabel->height(),
-                         300, ui->currentlyRunningLabel->y() + ui->currentlyRunningLabel->height());
-    }
-
-    if (ui->recentlyLoadedLabel->isVisible()) {
-        // line below the 'Recently loaded' label
-        painter.drawLine(36, ui->recentlyLoadedLabel->y() + ui->recentlyLoadedLabel->height(),
-                         300, ui->recentlyLoadedLabel->y() + ui->recentlyLoadedLabel->height());
-    }
-
-    painter.end();
-}
-
 void RunningScriptsWidget::scriptStopped(const QString& scriptName) {
-    _recentlyLoadedScripts.prepend(scriptName);
-}
-
-void RunningScriptsWidget::stopScript(int row, int column) {
-    if (column == 1) { // make sure the user has clicked on the close icon
-        _lastStoppedScript = _runningScriptsTable->item(row, 0)->toolTip();
-        emit stopScriptName(_runningScriptsTable->item(row, 0)->toolTip());
-    }
-}
-
-void RunningScriptsWidget::loadScript(int row, int column) {
-    Application::getInstance()->loadScript(_recentlyLoadedScriptsTable->item(row, column)->toolTip());
+    // _recentlyLoadedScripts.prepend(scriptName);
 }
 
 void RunningScriptsWidget::allScriptsStopped() {
     Application::getInstance()->stopAllScripts();
 }
-
-void RunningScriptsWidget::createRecentlyLoadedScriptsTable() {
-    if (!_recentlyLoadedScripts.contains(_lastStoppedScript) && !_lastStoppedScript.isEmpty()) {
-        _recentlyLoadedScripts.prepend(_lastStoppedScript);
-        _lastStoppedScript = "";
-    }
-
-    for (int i = 0; i < _recentlyLoadedScripts.size(); ++i) {
-        if (Application::getInstance()->getRunningScripts().contains(_recentlyLoadedScripts.at(i))) {
-            _recentlyLoadedScripts.removeOne(_recentlyLoadedScripts.at(i));
-        }
-    }
-
-    ui->recentlyLoadedLabel->setVisible(!_recentlyLoadedScripts.isEmpty());
-    ui->recentlyLoadedScriptsTableWidget->setVisible(!_recentlyLoadedScripts.isEmpty());
-    ui->recentlyLoadedInstruction->setVisible(!_recentlyLoadedScripts.isEmpty());
-
-    int limit = _recentlyLoadedScripts.size() > 9 ? 9 : _recentlyLoadedScripts.size();
-    _recentlyLoadedScriptsTable->setRowCount(limit);
-    for (int i = 0; i < limit; i++) {
-        QTableWidgetItem *scriptName = new QTableWidgetItem;
-        scriptName->setText(QString::number(i + 1) + ". " + QFileInfo(_recentlyLoadedScripts.at(i)).fileName());
-        scriptName->setToolTip(_recentlyLoadedScripts.at(i));
-        scriptName->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
-
-        _recentlyLoadedScriptsTable->setItem(i, 0, scriptName);
-    }
-
-    int y = ui->recentlyLoadedScriptsTableWidget->y() + 15;
-    for (int i = 0; i < _recentlyLoadedScriptsTable->rowCount(); ++i) {
-        y += _recentlyLoadedScriptsTable->rowHeight(i);
-    }
-
-    ui->recentlyLoadedInstruction->setGeometry(36, y,
-                                               ui->recentlyLoadedInstruction->width(),
-                                               ui->recentlyLoadedInstruction->height());
-
-    repaint();
-}
diff --git a/interface/src/ui/RunningScriptsWidget.h b/interface/src/ui/RunningScriptsWidget.h
index 14a1f4a58e..6810aca487 100644
--- a/interface/src/ui/RunningScriptsWidget.h
+++ b/interface/src/ui/RunningScriptsWidget.h
@@ -3,6 +3,7 @@
 //  interface/src/ui
 //
 //  Created by Mohammed Nafees on 03/28/2014.
+//  Updated by Ryan Huffman on 05/13/2014.
 //  Copyright 2014 High Fidelity, Inc.
 //
 //  Distributed under the Apache License, Version 2.0.
@@ -12,6 +13,11 @@
 #ifndef hifi_RunningScriptsWidget_h
 #define hifi_RunningScriptsWidget_h
 
+#include <QFileSystemModel>
+#include <QSignalMapper>
+#include <QSortFilterProxyModel>
+
+#include "ScriptsModel.h"
 #include "FramelessDialog.h"
 #include "ScriptsTableWidget.h"
 
@@ -32,27 +38,31 @@ signals:
     void stopScriptName(const QString& name);
 
 protected:
+    virtual bool eventFilter(QObject* sender, QEvent* event);
+
     virtual void keyPressEvent(QKeyEvent* event);
-    virtual void paintEvent(QPaintEvent* event);
+    virtual void showEvent(QShowEvent* event);
 
 public slots:
     void scriptStopped(const QString& scriptName);
     void setBoundary(const QRect& rect);
 
 private slots:
-    void stopScript(int row, int column);
-    void loadScript(int row, int column);
     void allScriptsStopped();
+    void updateFileFilter(const QString& filter);
+    void loadScriptFromList(const QModelIndex& index);
+    void loadSelectedScript();
+    void selectFirstInList();
 
 private:
     Ui::RunningScriptsWidget* ui;
-    ScriptsTableWidget* _runningScriptsTable;
+    QSignalMapper _signalMapper;
+    QSortFilterProxyModel _proxyModel;
+    ScriptsModel _scriptsModel;
     ScriptsTableWidget* _recentlyLoadedScriptsTable;
     QStringList _recentlyLoadedScripts;
     QString _lastStoppedScript;
     QRect _boundary;
-
-    void createRecentlyLoadedScriptsTable();
 };
 
 #endif // hifi_RunningScriptsWidget_h
diff --git a/interface/src/ui/ScriptEditorWidget.cpp b/interface/src/ui/ScriptEditorWidget.cpp
index be5577e0e8..513bbd899a 100644
--- a/interface/src/ui/ScriptEditorWidget.cpp
+++ b/interface/src/ui/ScriptEditorWidget.cpp
@@ -28,8 +28,12 @@
 
 ScriptEditorWidget::ScriptEditorWidget() :
     _scriptEditorWidgetUI(new Ui::ScriptEditorWidget),
-    _scriptEngine(NULL)
+    _scriptEngine(NULL),
+    _isRestarting(false),
+    _isReloading(false)
 {
+    setAttribute(Qt::WA_DeleteOnClose);
+
     _scriptEditorWidgetUI->setupUi(this);
 
     connect(_scriptEditorWidgetUI->scriptEdit->document(), &QTextDocument::modificationChanged, this,
@@ -51,15 +55,19 @@ ScriptEditorWidget::~ScriptEditorWidget() {
 }
 
 void ScriptEditorWidget::onScriptModified() {
-    if(_scriptEditorWidgetUI->onTheFlyCheckBox->isChecked() && isRunning()) {
+    if(_scriptEditorWidgetUI->onTheFlyCheckBox->isChecked() && isModified() && isRunning() && !_isReloading) {
+        _isRestarting = true;
         setRunning(false);
-        setRunning(true);
+        // Script is restarted once current script instance finishes.
     }
 }
 
-void ScriptEditorWidget::onScriptEnding() {
-    // signals will automatically be disonnected when the _scriptEngine is deleted later
+void ScriptEditorWidget::onScriptFinished(const QString& scriptPath) {
     _scriptEngine = NULL;
+    if (_isRestarting) {
+        _isRestarting = false;
+        setRunning(true);
+    }
 }
 
 bool ScriptEditorWidget::isModified() {
@@ -71,27 +79,28 @@ bool ScriptEditorWidget::isRunning() {
 }
 
 bool ScriptEditorWidget::setRunning(bool run) {
-    if (run && !save()) {
+    if (run && isModified() && !save()) {
         return false;
     }
 
-    // Clean-up old connections.
     if (_scriptEngine != NULL) {
         disconnect(_scriptEngine, &ScriptEngine::runningStateChanged, this, &ScriptEditorWidget::runningStateChanged);
         disconnect(_scriptEngine, &ScriptEngine::errorMessage, this, &ScriptEditorWidget::onScriptError);
         disconnect(_scriptEngine, &ScriptEngine::printedMessage, this, &ScriptEditorWidget::onScriptPrint);
-        disconnect(_scriptEngine, &ScriptEngine::scriptEnding, this, &ScriptEditorWidget::onScriptEnding);
+        disconnect(_scriptEngine, &ScriptEngine::update, this, &ScriptEditorWidget::onScriptModified);
+        disconnect(_scriptEngine, &ScriptEngine::finished, this, &ScriptEditorWidget::onScriptFinished);
     }
 
     if (run) {
-        _scriptEngine = Application::getInstance()->loadScript(_currentScript, true);
+        const QString& scriptURLString = QUrl(_currentScript).toString();
+        _scriptEngine = Application::getInstance()->loadScript(scriptURLString, true);
         connect(_scriptEngine, &ScriptEngine::runningStateChanged, this, &ScriptEditorWidget::runningStateChanged);
-
-        // Make new connections.
         connect(_scriptEngine, &ScriptEngine::errorMessage, this, &ScriptEditorWidget::onScriptError);
         connect(_scriptEngine, &ScriptEngine::printedMessage, this, &ScriptEditorWidget::onScriptPrint);
-        connect(_scriptEngine, &ScriptEngine::scriptEnding, this, &ScriptEditorWidget::onScriptEnding);
+        connect(_scriptEngine, &ScriptEngine::update, this, &ScriptEditorWidget::onScriptModified);
+        connect(_scriptEngine, &ScriptEngine::finished, this, &ScriptEditorWidget::onScriptFinished);
     } else {
+        connect(_scriptEngine, &ScriptEngine::finished, this, &ScriptEditorWidget::onScriptFinished);
         Application::getInstance()->stopScript(_currentScript);
         _scriptEngine = NULL;
     }
@@ -108,13 +117,14 @@ bool ScriptEditorWidget::saveFile(const QString &scriptPath) {
 
      QTextStream out(&file);
      out << _scriptEditorWidgetUI->scriptEdit->toPlainText();
+     file.close();
 
      setScriptFile(scriptPath);
      return true;
 }
 
 void ScriptEditorWidget::loadFile(const QString& scriptPath) {
-     QUrl url(scriptPath);
+    QUrl url(scriptPath);
 
     // if the scheme length is one or lower, maybe they typed in a file, let's try
     const int WINDOWS_DRIVE_LETTER_SIZE = 1;
@@ -127,13 +137,15 @@ void ScriptEditorWidget::loadFile(const QString& scriptPath) {
         }
         QTextStream in(&file);
         _scriptEditorWidgetUI->scriptEdit->setPlainText(in.readAll());
+        file.close();
         setScriptFile(scriptPath);
 
         if (_scriptEngine != NULL) {
             disconnect(_scriptEngine, &ScriptEngine::runningStateChanged, this, &ScriptEditorWidget::runningStateChanged);
             disconnect(_scriptEngine, &ScriptEngine::errorMessage, this, &ScriptEditorWidget::onScriptError);
             disconnect(_scriptEngine, &ScriptEngine::printedMessage, this, &ScriptEditorWidget::onScriptPrint);
-            disconnect(_scriptEngine, &ScriptEngine::scriptEnding, this, &ScriptEditorWidget::onScriptEnding);
+            disconnect(_scriptEngine, &ScriptEngine::update, this, &ScriptEditorWidget::onScriptModified);
+            disconnect(_scriptEngine, &ScriptEngine::finished, this, &ScriptEditorWidget::onScriptFinished);
         }
     } else {
         QNetworkAccessManager* networkManager = new QNetworkAccessManager(this);
@@ -148,12 +160,14 @@ void ScriptEditorWidget::loadFile(const QString& scriptPath) {
         }
     }
 
-    _scriptEngine = Application::getInstance()->getScriptEngine(_currentScript);
+    const QString& scriptURLString = QUrl(_currentScript).toString();
+    _scriptEngine = Application::getInstance()->getScriptEngine(scriptURLString);
     if (_scriptEngine != NULL) {
         connect(_scriptEngine, &ScriptEngine::runningStateChanged, this, &ScriptEditorWidget::runningStateChanged);
         connect(_scriptEngine, &ScriptEngine::errorMessage, this, &ScriptEditorWidget::onScriptError);
         connect(_scriptEngine, &ScriptEngine::printedMessage, this, &ScriptEditorWidget::onScriptPrint);
-        connect(_scriptEngine, &ScriptEngine::scriptEnding, this, &ScriptEditorWidget::onScriptEnding);
+        connect(_scriptEngine, &ScriptEngine::update, this, &ScriptEditorWidget::onScriptModified);
+        connect(_scriptEngine, &ScriptEngine::finished, this, &ScriptEditorWidget::onScriptFinished);
     }
 }
 
@@ -175,6 +189,7 @@ bool ScriptEditorWidget::saveAs() {
 
 void ScriptEditorWidget::setScriptFile(const QString& scriptPath) {
     _currentScript = scriptPath;
+    _currentScriptModified = QFileInfo(_currentScript).lastModified();
     _scriptEditorWidgetUI->scriptEdit->document()->setModified(false);
     setWindowModified(false);
 
@@ -198,3 +213,29 @@ void ScriptEditorWidget::onScriptError(const QString& message) {
 void ScriptEditorWidget::onScriptPrint(const QString& message) {
     _scriptEditorWidgetUI->debugText->appendPlainText("> " + message);
 }
+
+void ScriptEditorWidget::onWindowActivated() {
+    if (!_isReloading) {
+        _isReloading = true;
+        
+        if (QFileInfo(_currentScript).lastModified() > _currentScriptModified) {
+            if (static_cast<ScriptEditorWindow*>(this->parent()->parent()->parent())->autoReloadScripts()
+                || QMessageBox::warning(this, _currentScript,
+                    tr("This file has been modified outside of the Interface editor.") + "\n\n"
+                        + (isModified()
+                        ? tr("Do you want to reload it and lose the changes you've made in the Interface editor?")
+                        : tr("Do you want to reload it?")),
+                    QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
+                loadFile(_currentScript);
+                if (_scriptEditorWidgetUI->onTheFlyCheckBox->isChecked() && isRunning()) {
+                    _isRestarting = true;
+                    setRunning(false);
+                    // Script is restarted once current script instance finishes.
+                }
+
+            }
+        }
+
+        _isReloading = false;
+    }
+}
diff --git a/interface/src/ui/ScriptEditorWidget.h b/interface/src/ui/ScriptEditorWidget.h
index 3e95ea322b..8dd847ee6d 100644
--- a/interface/src/ui/ScriptEditorWidget.h
+++ b/interface/src/ui/ScriptEditorWidget.h
@@ -13,6 +13,7 @@
 #define hifi_ScriptEditorWidget_h
 
 #include <QDockWidget>
+
 #include "ScriptEngine.h"
 
 namespace Ui {
@@ -42,16 +43,22 @@ signals:
     void scriptnameChanged();
     void scriptModified();
 
+public slots:
+    void onWindowActivated();
+
 private slots:
     void onScriptError(const QString& message);
     void onScriptPrint(const QString& message);
     void onScriptModified();
-    void onScriptEnding();
+    void onScriptFinished(const QString& scriptName);
 
 private:
     Ui::ScriptEditorWidget* _scriptEditorWidgetUI;
     ScriptEngine* _scriptEngine;
     QString _currentScript;
+    QDateTime _currentScriptModified;
+    bool _isRestarting;
+    bool _isReloading;
 };
 
 #endif // hifi_ScriptEditorWidget_h
diff --git a/interface/src/ui/ScriptEditorWindow.cpp b/interface/src/ui/ScriptEditorWindow.cpp
index 3f63f0741b..895d725699 100644
--- a/interface/src/ui/ScriptEditorWindow.cpp
+++ b/interface/src/ui/ScriptEditorWindow.cpp
@@ -36,6 +36,8 @@ ScriptEditorWindow::ScriptEditorWindow() :
     _loadMenu(new QMenu),
     _saveMenu(new QMenu)
 {
+    setAttribute(Qt::WA_DeleteOnClose);
+
     _ScriptEditorWindowUI->setupUi(this);
     this->setWindowFlags(Qt::Tool);
     show();
@@ -140,6 +142,7 @@ ScriptEditorWidget* ScriptEditorWindow::addScriptEditorWidget(QString title) {
     connect(newScriptEditorWidget, &ScriptEditorWidget::scriptnameChanged, this, &ScriptEditorWindow::updateScriptNameOrStatus);
     connect(newScriptEditorWidget, &ScriptEditorWidget::scriptModified, this, &ScriptEditorWindow::updateScriptNameOrStatus);
     connect(newScriptEditorWidget, &ScriptEditorWidget::runningStateChanged, this, &ScriptEditorWindow::updateButtons);
+    connect(this, &ScriptEditorWindow::windowActivated, newScriptEditorWidget, &ScriptEditorWidget::onWindowActivated);
     _ScriptEditorWindowUI->tabWidget->addTab(newScriptEditorWidget, title);
     _ScriptEditorWindowUI->tabWidget->setCurrentWidget(newScriptEditorWidget);
     newScriptEditorWidget->setUpdatesEnabled(true);
@@ -216,3 +219,15 @@ void ScriptEditorWindow::terminateCurrentTab() {
         this->raise();
     }
 }
+
+bool ScriptEditorWindow::autoReloadScripts() {
+    return _ScriptEditorWindowUI->autoReloadCheckBox->isChecked();
+}
+
+bool ScriptEditorWindow::event(QEvent* event) {
+    if (event->type() == QEvent::WindowActivate) {
+        emit windowActivated();
+    }
+    return QWidget::event(event);
+}
+
diff --git a/interface/src/ui/ScriptEditorWindow.h b/interface/src/ui/ScriptEditorWindow.h
index 360e902cc2..1915014b69 100644
--- a/interface/src/ui/ScriptEditorWindow.h
+++ b/interface/src/ui/ScriptEditorWindow.h
@@ -26,9 +26,14 @@ public:
     ~ScriptEditorWindow();
 
     void terminateCurrentTab();
+    bool autoReloadScripts();
+
+signals:
+    void windowActivated();
 
 protected:
     void closeEvent(QCloseEvent* event);
+    virtual bool event(QEvent* event);
 
 private:
     Ui::ScriptEditorWindow* _ScriptEditorWindowUI;
diff --git a/interface/src/ui/ScriptsTableWidget.cpp b/interface/src/ui/ScriptsTableWidget.cpp
index 95acca052c..7b4f9e6b1f 100644
--- a/interface/src/ui/ScriptsTableWidget.cpp
+++ b/interface/src/ui/ScriptsTableWidget.cpp
@@ -23,7 +23,7 @@ ScriptsTableWidget::ScriptsTableWidget(QWidget* parent) :
     setShowGrid(false);
     setSelectionMode(QAbstractItemView::NoSelection);
     setEditTriggers(QAbstractItemView::NoEditTriggers);
-    setStyleSheet("QTableWidget { background: transparent; color: #333333; } QToolTip { color: #000000; background: #f9f6e4; padding: 2px;  }");
+    setStyleSheet("QTableWidget { border: none; background: transparent; color: #333333; } QToolTip { color: #000000; background: #f9f6e4; padding: 2px;  }");
     setToolTipDuration(200);
     setWordWrap(true);
     setGeometry(0, 0, parent->width(), parent->height());
diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp
index fa62ecdb9b..379dd35df7 100644
--- a/interface/src/ui/Stats.cpp
+++ b/interface/src/ui/Stats.cpp
@@ -286,11 +286,16 @@ void Stats::display(
             pingVoxel = totalPingVoxel/voxelServerCount;
         }
 
-        lines = _expanded ? 4 : 3;
+
+        Audio* audio = Application::getInstance()->getAudio();
+        const AudioStreamStats& audioMixerAvatarStreamStats = audio->getAudioMixerAvatarStreamStats();
+        const QHash<QUuid, AudioStreamStats>& audioMixerInjectedStreamStatsMap = audio->getAudioMixerInjectedStreamStatsMap();
+
+        lines = _expanded ? 10 + audioMixerInjectedStreamStatsMap.size(): 3;
         drawBackground(backgroundColor, horizontalOffset, 0, _pingStatsWidth, lines * STATS_PELS_PER_LINE + 10);
         horizontalOffset += 5;
 
-        Audio* audio = Application::getInstance()->getAudio();
+        
 
         char audioJitter[30];
         sprintf(audioJitter,
@@ -299,10 +304,9 @@ void Stats::display(
                 (float) audio->getNetworkSampleRate() * 1000.f);
         drawText(30, glWidget->height() - 22, scale, rotation, font, audioJitter, color);
         
-                 
+        
         char audioPing[30];
         sprintf(audioPing, "Audio ping: %d", pingAudio);
-       
                  
         char avatarPing[30];
         sprintf(avatarPing, "Avatar ping: %d", pingAvatar);
@@ -322,12 +326,54 @@ void Stats::display(
 
             verticalOffset += STATS_PELS_PER_LINE;
             drawText(horizontalOffset, verticalOffset, scale, rotation, font, voxelMaxPing, color);
+
+            char audioMixerStatsLabelString[] = "AudioMixer stats:";
+            char streamStatsFormatLabelString[] = "early/late/lost, jframes";
+            
+            verticalOffset += STATS_PELS_PER_LINE;
+            drawText(horizontalOffset, verticalOffset, scale, rotation, font, audioMixerStatsLabelString, color);
+            verticalOffset += STATS_PELS_PER_LINE;
+            drawText(horizontalOffset, verticalOffset, scale, rotation, font, streamStatsFormatLabelString, color);
+
+
+            char downstreamLabelString[] = " Downstream:";
+            verticalOffset += STATS_PELS_PER_LINE;
+            drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamLabelString, color);
+
+            const SequenceNumberStats& downstreamAudioSequenceNumberStats = audio->getIncomingMixedAudioSequenceNumberStats();
+            char downstreamAudioStatsString[30];
+            sprintf(downstreamAudioStatsString, "  mix: %d/%d/%d, %d", downstreamAudioSequenceNumberStats.getNumEarly(),
+                downstreamAudioSequenceNumberStats.getNumLate(), downstreamAudioSequenceNumberStats.getNumLost(),
+                audio->getJitterBufferSamples() / NETWORK_BUFFER_LENGTH_SAMPLES_STEREO);
+
+            verticalOffset += STATS_PELS_PER_LINE;
+            drawText(horizontalOffset, verticalOffset, scale, rotation, font, downstreamAudioStatsString, color);
+            
+            char upstreamLabelString[] = " Upstream:";
+            verticalOffset += STATS_PELS_PER_LINE;
+            drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamLabelString, color);
+
+            char upstreamAudioStatsString[30];
+            sprintf(upstreamAudioStatsString, "  mic: %d/%d/%d, %d", audioMixerAvatarStreamStats._packetsEarly,
+                audioMixerAvatarStreamStats._packetsLate, audioMixerAvatarStreamStats._packetsLost,
+                audioMixerAvatarStreamStats._jitterBufferFrames);
+
+            verticalOffset += STATS_PELS_PER_LINE;
+            drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color);
+
+            foreach(AudioStreamStats injectedStreamStats, audioMixerInjectedStreamStatsMap) {
+                sprintf(upstreamAudioStatsString, "  inj: %d/%d/%d, %d", injectedStreamStats._packetsEarly,
+                    injectedStreamStats._packetsLate, injectedStreamStats._packetsLost, injectedStreamStats._jitterBufferFrames);
+                
+                verticalOffset += STATS_PELS_PER_LINE;
+                drawText(horizontalOffset, verticalOffset, scale, rotation, font, upstreamAudioStatsString, color);
+            }
         }
 
         verticalOffset = 0;
         horizontalOffset = _lastHorizontalOffset + _generalStatsWidth + _pingStatsWidth + 2;
     }
-
+    
     MyAvatar* myAvatar = Application::getInstance()->getAvatar();
     glm::vec3 avatarPos = myAvatar->getPosition();
 
diff --git a/interface/src/ui/overlays/BillboardOverlay.cpp b/interface/src/ui/overlays/BillboardOverlay.cpp
new file mode 100644
index 0000000000..40de565155
--- /dev/null
+++ b/interface/src/ui/overlays/BillboardOverlay.cpp
@@ -0,0 +1,136 @@
+//
+//  BillboardOverlay.cpp
+//
+//
+//  Created by Clement on 7/1/14.
+//  Copyright 2014 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
+//
+
+#include "../../Application.h"
+
+#include "BillboardOverlay.h"
+
+BillboardOverlay::BillboardOverlay()
+: _manager(NULL),
+  _scale(1.0f),
+  _isFacingAvatar(true) {
+}
+
+void BillboardOverlay::render() {
+    if (_billboard.isEmpty()) {
+        return;
+    }
+    if (!_billboardTexture) {
+        QImage image = QImage::fromData(_billboard);
+        if (image.format() != QImage::Format_ARGB32) {
+            image = image.convertToFormat(QImage::Format_ARGB32);
+        }
+        _size = image.size();
+        _billboardTexture.reset(new Texture());
+        glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID());
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _size.width(), _size.height(), 0,
+                     GL_BGRA, GL_UNSIGNED_BYTE, image.constBits());
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+        
+    } else {
+        glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID());
+    }
+    
+    glEnable(GL_ALPHA_TEST);
+    glAlphaFunc(GL_GREATER, 0.5f);
+    
+    glEnable(GL_TEXTURE_2D);
+    glDisable(GL_LIGHTING);
+    
+    glPushMatrix(); {
+        glTranslatef(_position.x, _position.y, _position.z);
+        if (_isFacingAvatar) {
+            // rotate about vertical to face the camera
+            glm::quat rotation = Application::getInstance()->getCamera()->getRotation();
+            rotation *= glm::angleAxis(glm::pi<float>(), glm::vec3(0.0f, 1.0f, 0.0f));
+            glm::vec3 axis = glm::axis(rotation);
+            glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
+        } else {
+            glm::vec3 axis = glm::axis(_rotation);
+            glRotatef(glm::degrees(glm::angle(_rotation)), axis.x, axis.y, axis.z);
+        }
+        glScalef(_scale, _scale, _scale);
+        
+        float maxSize = glm::max(_size.width(), _size.height());
+        float x = _size.width() / (2.0f * maxSize);
+        float y = -_size.height() / (2.0f * maxSize);
+        
+        glColor3f(1.0f, 1.0f, 1.0f);
+        glBegin(GL_QUADS); {
+            glTexCoord2f(0.0f, 0.0f);
+            glVertex2f(-x, -y);
+            glTexCoord2f(1.0f, 0.0f);
+            glVertex2f(x, -y);
+            glTexCoord2f(1.0f, 1.0f);
+            glVertex2f(x, y);
+            glTexCoord2f(0.0f, 1.0f);
+            glVertex2f(-x, y);
+        } glEnd();
+        
+    } glPopMatrix();
+    
+    glDisable(GL_TEXTURE_2D);
+    glEnable(GL_LIGHTING);
+    glDisable(GL_ALPHA_TEST);
+    
+    glBindTexture(GL_TEXTURE_2D, 0);
+}
+
+void BillboardOverlay::setProperties(const QScriptValue &properties) {
+    Base3DOverlay::setProperties(properties);
+    
+    QScriptValue urlValue = properties.property("url");
+    if (urlValue.isValid()) {
+        _url = urlValue.toVariant().toString();
+        
+        setBillboardURL(_url);
+    }
+    
+    QScriptValue scaleValue = properties.property("scale");
+    if (scaleValue.isValid()) {
+        _scale = scaleValue.toVariant().toFloat();
+    }
+    
+    QScriptValue rotationValue = properties.property("rotation");
+    if (rotationValue.isValid()) {
+        QScriptValue x = rotationValue.property("x");
+        QScriptValue y = rotationValue.property("y");
+        QScriptValue z = rotationValue.property("z");
+        QScriptValue w = rotationValue.property("w");
+        if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) {
+            _rotation.x = x.toVariant().toFloat();
+            _rotation.y = y.toVariant().toFloat();
+            _rotation.z = z.toVariant().toFloat();
+            _rotation.w = w.toVariant().toFloat();
+        }
+    }
+    
+    QScriptValue isFacingAvatarValue = properties.property("isFacingAvatar");
+    if (isFacingAvatarValue.isValid()) {
+        _isFacingAvatar = isFacingAvatarValue.toVariant().toBool();
+    }
+}
+
+// TODO: handle setting image multiple times, how do we manage releasing the bound texture?
+void BillboardOverlay::setBillboardURL(const QUrl url) {
+    // TODO: are we creating too many QNetworkAccessManager() when multiple calls to setImageURL are made?
+    _manager->deleteLater();
+    _manager = new QNetworkAccessManager();
+    connect(_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
+    _manager->get(QNetworkRequest(url));
+}
+
+void BillboardOverlay::replyFinished(QNetworkReply* reply) {
+    // replace our byte array with the downloaded data
+    _billboard = reply->readAll();
+    _manager->deleteLater();
+    _manager = NULL;
+}
diff --git a/interface/src/ui/overlays/BillboardOverlay.h b/interface/src/ui/overlays/BillboardOverlay.h
new file mode 100644
index 0000000000..473e8a066f
--- /dev/null
+++ b/interface/src/ui/overlays/BillboardOverlay.h
@@ -0,0 +1,46 @@
+//
+//  BillboardOverlay.h
+//
+//
+//  Created by Clement on 7/1/14.
+//  Copyright 2014 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
+//
+
+#ifndef hifi_BillboardOverlay_h
+#define hifi_BillboardOverlay_h
+
+#include <QScopedPointer>
+#include <QUrl>
+
+#include "Base3DOverlay.h"
+#include "../../renderer/TextureCache.h"
+
+class BillboardOverlay : public Base3DOverlay {
+    Q_OBJECT
+public:
+    BillboardOverlay();
+    
+    virtual void render();
+    virtual void setProperties(const QScriptValue& properties);
+
+private slots:
+    void replyFinished(QNetworkReply* reply);
+
+private:
+    void setBillboardURL(const QUrl url);
+    
+    QNetworkAccessManager* _manager;
+    QUrl _url;
+    QByteArray _billboard;
+    QSize _size;
+    QScopedPointer<Texture> _billboardTexture;
+    
+    glm::quat _rotation;
+    float _scale;
+    bool _isFacingAvatar;
+};
+
+#endif // hifi_BillboardOverlay_h
\ No newline at end of file
diff --git a/interface/src/ui/overlays/ImageOverlay.cpp b/interface/src/ui/overlays/ImageOverlay.cpp
index aa4766488a..79b1b23de5 100644
--- a/interface/src/ui/overlays/ImageOverlay.cpp
+++ b/interface/src/ui/overlays/ImageOverlay.cpp
@@ -19,7 +19,7 @@
 #include "ImageOverlay.h"
 
 ImageOverlay::ImageOverlay() :
-    _manager(0),
+    _manager(NULL),
     _textureID(0),
     _renderImage(false),
     _textureBound(false),
@@ -37,6 +37,7 @@ ImageOverlay::~ImageOverlay() {
 // TODO: handle setting image multiple times, how do we manage releasing the bound texture?
 void ImageOverlay::setImageURL(const QUrl& url) {
     // TODO: are we creating too many QNetworkAccessManager() when multiple calls to setImageURL are made?
+    _manager->deleteLater();
     _manager = new QNetworkAccessManager();
     connect(_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
     _manager->get(QNetworkRequest(url));
@@ -49,6 +50,7 @@ void ImageOverlay::replyFinished(QNetworkReply* reply) {
     _textureImage.loadFromData(rawData);
     _renderImage = true;
     _manager->deleteLater();
+    _manager = NULL;
 }
 
 void ImageOverlay::render() {
diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp
new file mode 100644
index 0000000000..bc0cc720c2
--- /dev/null
+++ b/interface/src/ui/overlays/ModelOverlay.cpp
@@ -0,0 +1,115 @@
+//
+//  ModelOverlay.cpp
+//
+//
+//  Created by Clement on 6/30/14.
+//  Copyright 2014 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
+//
+#include "../../Menu.h"
+
+#include "ModelOverlay.h"
+
+ModelOverlay::ModelOverlay()
+    : _model(),
+      _scale(1.0f),
+      _updateModel(false) {
+    _model.init();
+}
+
+void ModelOverlay::update(float deltatime) {
+    if (_updateModel) {
+        _updateModel = false;
+        
+        _model.setScaleToFit(true, _scale);
+        _model.setSnapModelToCenter(true);
+        _model.setRotation(_rotation);
+        _model.setTranslation(_position);
+        _model.setURL(_url);
+        _model.simulate(deltatime, true);
+    } else {
+        _model.simulate(deltatime);
+    }
+}
+
+void ModelOverlay::render() {
+    if (_model.isActive()) {
+        
+        if (_model.isRenderable()) {
+            _model.render(_alpha);
+        }
+        bool displayModelBounds = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelBounds);
+        if (displayModelBounds) {
+            glm::vec3 unRotatedMinimum = _model.getUnscaledMeshExtents().minimum;
+            glm::vec3 unRotatedMaximum = _model.getUnscaledMeshExtents().maximum;
+            glm::vec3 unRotatedExtents = unRotatedMaximum - unRotatedMinimum;
+            
+            float width = unRotatedExtents.x;
+            float height = unRotatedExtents.y;
+            float depth = unRotatedExtents.z;
+            
+            Extents rotatedExtents = _model.getUnscaledMeshExtents();
+            calculateRotatedExtents(rotatedExtents, _rotation);
+            
+            glm::vec3 rotatedSize = rotatedExtents.maximum - rotatedExtents.minimum;
+            
+            const glm::vec3& modelScale = _model.getScale();
+            
+            glPushMatrix(); {
+                glTranslatef(_position.x, _position.y, _position.z);
+                
+                // draw the rotated bounding cube
+                glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
+                glPushMatrix(); {
+                    glScalef(rotatedSize.x * modelScale.x, rotatedSize.y * modelScale.y, rotatedSize.z * modelScale.z);
+                    glutWireCube(1.0);
+                } glPopMatrix();
+                
+                // draw the model relative bounding box
+                glm::vec3 axis = glm::axis(_rotation);
+                glRotatef(glm::degrees(glm::angle(_rotation)), axis.x, axis.y, axis.z);
+                glScalef(width * modelScale.x, height * modelScale.y, depth * modelScale.z);
+                glColor3f(0.0f, 1.0f, 0.0f);
+                glutWireCube(1.0);
+                
+            } glPopMatrix();
+        }
+    }
+}
+
+void ModelOverlay::setProperties(const QScriptValue &properties) {
+    Base3DOverlay::setProperties(properties);
+    
+    QScriptValue urlValue = properties.property("url");
+    if (urlValue.isValid()) {
+        _url = urlValue.toVariant().toString();
+        _updateModel = true;
+    }
+    
+    QScriptValue scaleValue = properties.property("scale");
+    if (scaleValue.isValid()) {
+        _scale = scaleValue.toVariant().toFloat();
+        _updateModel = true;
+    }
+    
+    QScriptValue rotationValue = properties.property("rotation");
+    if (rotationValue.isValid()) {
+        QScriptValue x = rotationValue.property("x");
+        QScriptValue y = rotationValue.property("y");
+        QScriptValue z = rotationValue.property("z");
+        QScriptValue w = rotationValue.property("w");
+        if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) {
+            _rotation.x = x.toVariant().toFloat();
+            _rotation.y = y.toVariant().toFloat();
+            _rotation.z = z.toVariant().toFloat();
+            _rotation.w = w.toVariant().toFloat();
+        }
+        _updateModel = true;
+    }
+    
+    if (properties.property("position").isValid()) {
+        _updateModel = true;
+    }
+}
\ No newline at end of file
diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h
new file mode 100644
index 0000000000..e0f979676f
--- /dev/null
+++ b/interface/src/ui/overlays/ModelOverlay.h
@@ -0,0 +1,38 @@
+//
+//  ModelOverlay.h
+//
+//
+//  Created by Clement on 6/30/14.
+//  Copyright 2014 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
+//
+
+#ifndef hifi_ModelOverlay_h
+#define hifi_ModelOverlay_h
+
+#include "Base3DOverlay.h"
+
+#include "../../renderer/Model.h"
+
+class ModelOverlay : public Base3DOverlay {
+    Q_OBJECT
+public:
+    ModelOverlay();
+    
+    virtual void update(float deltatime);
+    virtual void render();
+    virtual void setProperties(const QScriptValue& properties);
+private:
+    
+    Model _model;
+    
+    QUrl _url;
+    glm::quat _rotation;
+    float _scale;
+    
+    bool _updateModel;
+};
+
+#endif // hifi_ModelOverlay_h
\ No newline at end of file
diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp
index 95f4f2b2fe..dd483da27a 100644
--- a/interface/src/ui/overlays/Overlays.cpp
+++ b/interface/src/ui/overlays/Overlays.cpp
@@ -10,13 +10,15 @@
 
 #include <Application.h>
 
+#include "BillboardOverlay.h"
 #include "Cube3DOverlay.h"
 #include "ImageOverlay.h"
 #include "Line3DOverlay.h"
+#include "LocalVoxelsOverlay.h"
+#include "ModelOverlay.h"
 #include "Overlays.h"
 #include "Sphere3DOverlay.h"
 #include "TextOverlay.h"
-#include "LocalVoxelsOverlay.h"
 
 Overlays::Overlays() : _nextOverlayID(1) {
 }
@@ -156,6 +158,18 @@ unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& prope
         thisOverlay->setProperties(properties);
         created = true;
         is3D = true;
+    } else if (type == "model") {
+        thisOverlay = new ModelOverlay();
+        thisOverlay->init(_parent);
+        thisOverlay->setProperties(properties);
+        created = true;
+        is3D = true;
+    } else if (type == "billboard") {
+        thisOverlay = new BillboardOverlay();
+        thisOverlay->init(_parent);
+        thisOverlay->setProperties(properties);
+        created = true;
+        is3D = true;
     }
 
     if (created) {
diff --git a/interface/ui/preferencesDialog.ui b/interface/ui/preferencesDialog.ui
index f00d7c4788..95678bf6f8 100644
--- a/interface/ui/preferencesDialog.ui
+++ b/interface/ui/preferencesDialog.ui
@@ -154,9 +154,9 @@ color: #0e7077</string>
     <property name="geometry">
      <rect>
       <x>0</x>
-      <y>-1002</y>
-      <width>477</width>
-      <height>1386</height>
+      <y>0</y>
+      <width>600</width>
+      <height>1091</height>
      </rect>
     </property>
     <layout class="QVBoxLayout" name="verticalLayout_2">
@@ -645,6 +645,112 @@ color: #0e7077</string>
        </property>
       </widget>
      </item>
+     <item>
+      <widget class="QLabel" name="headLabel_4">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="minimumSize">
+        <size>
+         <width>0</width>
+         <height>30</height>
+        </size>
+       </property>
+       <property name="font">
+        <font>
+         <family>Arial</family>
+         <pointsize>16</pointsize>
+        </font>
+       </property>
+       <property name="styleSheet">
+        <string notr="true">color: #0e7077</string>
+       </property>
+       <property name="text">
+        <string>Load scripts from this directory:</string>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
+       </property>
+       <property name="margin">
+        <number>0</number>
+       </property>
+       <property name="buddy">
+        <cstring>snapshotLocationEdit</cstring>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <layout class="QHBoxLayout" name="horizontalLayout_11">
+       <item>
+        <widget class="QLineEdit" name="scriptsLocationEdit">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="font">
+          <font>
+           <family>Arial</family>
+          </font>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <spacer name="horizontalSpacer_11">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeType">
+          <enum>QSizePolicy::Fixed</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item>
+        <widget class="QPushButton" name="buttonBrowseScriptsLocation">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="minimumSize">
+          <size>
+           <width>30</width>
+           <height>30</height>
+          </size>
+         </property>
+         <property name="maximumSize">
+          <size>
+           <width>30</width>
+           <height>30</height>
+          </size>
+         </property>
+         <property name="styleSheet">
+          <string notr="true"/>
+         </property>
+         <property name="text">
+          <string/>
+         </property>
+         <property name="iconSize">
+          <size>
+           <width>30</width>
+           <height>30</height>
+          </size>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </item>
      <item>
       <widget class="QWidget" name="widget" native="true">
        <layout class="QHBoxLayout" name="horizontalLayout_12">
@@ -660,7 +766,7 @@ color: #0e7077</string>
            <string notr="true">background: #0e7077;
 color: #fff;
 border-radius: 4px;
-font: bold 14pt;
+font: bold 14px;
 padding: 10px;margin-top:10px</string>
           </property>
           <property name="text">
@@ -796,7 +902,7 @@ padding: 10px;margin-top:10px</string>
       </widget>
      </item>
      <item>
-      <layout class="QHBoxLayout" name="horizontalLayout_11">
+      <layout class="QHBoxLayout" name="horizontalLayout_111">
        <property name="spacing">
         <number>0</number>
        </property>
@@ -831,7 +937,7 @@ padding: 10px;margin-top:10px</string>
         </widget>
        </item>
        <item>
-        <spacer name="horizontalSpacer_11">
+        <spacer name="horizontalSpacer_111">
          <property name="font">
           <font>
            <family>Arial</family>
diff --git a/interface/ui/runningScriptsWidget.ui b/interface/ui/runningScriptsWidget.ui
index 6cb23f4c89..71efe6970a 100644
--- a/interface/ui/runningScriptsWidget.ui
+++ b/interface/ui/runningScriptsWidget.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>323</width>
-    <height>894</height>
+    <width>324</width>
+    <height>971</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -21,240 +21,623 @@ QWidget {
 	background: #f7f7f7; 
 }</string>
   </property>
-  <widget class="QLabel" name="widgetTitle">
-   <property name="geometry">
-    <rect>
-     <x>37</x>
-     <y>29</y>
-     <width>251</width>
-     <height>20</height>
-    </rect>
+  <layout class="QVBoxLayout" name="verticalLayout_2">
+   <property name="leftMargin">
+    <number>20</number>
    </property>
-   <property name="styleSheet">
-    <string notr="true">color: #0e7077;
-font-size: 20pt;
+   <property name="topMargin">
+    <number>20</number>
+   </property>
+   <property name="rightMargin">
+    <number>20</number>
+   </property>
+   <property name="bottomMargin">
+    <number>20</number>
+   </property>
+   <item>
+    <widget class="QWidget" name="header" native="true">
+     <layout class="QHBoxLayout" name="horizontalLayout">
+      <property name="leftMargin">
+       <number>0</number>
+      </property>
+      <property name="topMargin">
+       <number>0</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <item>
+       <widget class="QLabel" name="widgetTitle">
+        <property name="styleSheet">
+         <string notr="true">color: #0e7077;
+font-size: 20px;
 background: transparent;</string>
-   </property>
-   <property name="text">
-    <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:18pt;&quot;&gt;Running Scripts&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-   </property>
-   <property name="margin">
-    <number>0</number>
-   </property>
-   <property name="indent">
-    <number>-1</number>
-   </property>
-  </widget>
-  <widget class="QLabel" name="currentlyRunningLabel">
-   <property name="geometry">
-    <rect>
-     <x>36</x>
-     <y>110</y>
-     <width>270</width>
-     <height>20</height>
-    </rect>
-   </property>
-   <property name="styleSheet">
-    <string notr="true">color: #0e7077;
-font: bold 14pt;
+        </property>
+        <property name="text">
+         <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:18px;&quot;&gt;Running Scripts&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+        </property>
+        <property name="margin">
+         <number>0</number>
+        </property>
+        <property name="indent">
+         <number>-1</number>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <spacer name="horizontalSpacer">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>40</width>
+          <height>20</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+      <item>
+       <widget class="QPushButton" name="hideWidgetButton">
+        <property name="cursor">
+         <cursorShape>PointingHandCursor</cursorShape>
+        </property>
+        <property name="styleSheet">
+         <string notr="true">border: 0</string>
+        </property>
+        <property name="text">
+         <string/>
+        </property>
+        <property name="iconSize">
+         <size>
+          <width>16</width>
+          <height>16</height>
+         </size>
+        </property>
+        <property name="flat">
+         <bool>true</bool>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QWidget" name="runningScriptsArea" native="true">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+       <horstretch>0</horstretch>
+       <verstretch>1</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="minimumSize">
+      <size>
+       <width>0</width>
+       <height>141</height>
+      </size>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_4">
+      <property name="spacing">
+       <number>0</number>
+      </property>
+      <property name="leftMargin">
+       <number>0</number>
+      </property>
+      <property name="topMargin">
+       <number>0</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+      <item>
+       <widget class="QLabel" name="currentlyRunningLabel">
+        <property name="font">
+         <font>
+          <family>Helvetica,Arial,sans-serif</family>
+          <pointsize>16</pointsize>
+          <weight>75</weight>
+          <italic>false</italic>
+          <bold>true</bold>
+         </font>
+        </property>
+        <property name="styleSheet">
+         <string notr="true">color: #0e7077;
+font: bold 16px;
 background: transparent;</string>
-   </property>
-   <property name="text">
-    <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Currently running&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-   </property>
-  </widget>
-  <widget class="QPushButton" name="reloadAllButton">
-   <property name="geometry">
-    <rect>
-     <x>36</x>
-     <y>270</y>
-     <width>111</width>
-     <height>35</height>
-    </rect>
-   </property>
-   <property name="cursor">
-    <cursorShape>PointingHandCursor</cursorShape>
-   </property>
-   <property name="autoFillBackground">
-    <bool>false</bool>
-   </property>
-   <property name="styleSheet">
-    <string notr="true">background: #0e7077;
+        </property>
+        <property name="text">
+         <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Currently running&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <spacer name="verticalSpacer_3">
+        <property name="orientation">
+         <enum>Qt::Vertical</enum>
+        </property>
+        <property name="sizeType">
+         <enum>QSizePolicy::Minimum</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>0</width>
+          <height>8</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+      <item>
+       <widget class="QWidget" name="reloadStopButtonArea" native="true">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <layout class="QHBoxLayout" name="horizontalLayout_3">
+         <property name="spacing">
+          <number>24</number>
+         </property>
+         <property name="leftMargin">
+          <number>0</number>
+         </property>
+         <property name="topMargin">
+          <number>0</number>
+         </property>
+         <property name="rightMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>0</number>
+         </property>
+         <item>
+          <widget class="QPushButton" name="reloadAllButton">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="minimumSize">
+            <size>
+             <width>111</width>
+             <height>35</height>
+            </size>
+           </property>
+           <property name="cursor">
+            <cursorShape>PointingHandCursor</cursorShape>
+           </property>
+           <property name="autoFillBackground">
+            <bool>false</bool>
+           </property>
+           <property name="styleSheet">
+            <string notr="true">background: #0e7077;
 color: #fff;
 border-radius: 4px;
-font: bold 14pt;
+font: bold 14px;
 padding-top: 3px;</string>
-   </property>
-   <property name="text">
-    <string>Reload all</string>
-   </property>
-  </widget>
-  <widget class="QPushButton" name="stopAllButton">
-   <property name="geometry">
-    <rect>
-     <x>160</x>
-     <y>270</y>
-     <width>93</width>
-     <height>35</height>
-    </rect>
-   </property>
-   <property name="cursor">
-    <cursorShape>PointingHandCursor</cursorShape>
-   </property>
-   <property name="styleSheet">
-    <string notr="true">background: #0e7077;
+           </property>
+           <property name="text">
+            <string>Reload all</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="stopAllButton">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="minimumSize">
+            <size>
+             <width>111</width>
+             <height>35</height>
+            </size>
+           </property>
+           <property name="cursor">
+            <cursorShape>PointingHandCursor</cursorShape>
+           </property>
+           <property name="styleSheet">
+            <string notr="true">background: #0e7077;
 color: #fff;
 border-radius: 4px;
-font: bold 14pt;
+font: bold 14px;
 padding-top: 3px;</string>
-   </property>
-   <property name="text">
-    <string>Stop all</string>
-   </property>
-  </widget>
-  <widget class="QLabel" name="recentlyLoadedLabel">
-   <property name="geometry">
-    <rect>
-     <x>36</x>
-     <y>320</y>
-     <width>265</width>
-     <height>20</height>
-    </rect>
-   </property>
-   <property name="styleSheet">
-    <string notr="true">color: #0e7077;
-font: bold 14pt;</string>
-   </property>
-   <property name="text">
-    <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Recently loaded&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-   </property>
-  </widget>
-  <widget class="QLabel" name="recentlyLoadedInstruction">
-   <property name="geometry">
-    <rect>
-     <x>36</x>
-     <y>630</y>
-     <width>211</width>
-     <height>41</height>
-    </rect>
-   </property>
-   <property name="styleSheet">
-    <string notr="true">color: #95a5a6;
-font-size: 14pt;</string>
-   </property>
-   <property name="text">
-    <string>(click a script to load and run it)</string>
-   </property>
-   <property name="wordWrap">
-    <bool>true</bool>
-   </property>
-  </widget>
-  <widget class="QPushButton" name="hideWidgetButton">
-   <property name="geometry">
-    <rect>
-     <x>285</x>
-     <y>29</y>
-     <width>16</width>
-     <height>16</height>
-    </rect>
-   </property>
-   <property name="cursor">
-    <cursorShape>PointingHandCursor</cursorShape>
-   </property>
-   <property name="text">
-    <string/>
-   </property>
-   <property name="iconSize">
-    <size>
-     <width>16</width>
-     <height>16</height>
-    </size>
-   </property>
-   <property name="flat">
-    <bool>true</bool>
-   </property>
-  </widget>
-  <widget class="QLabel" name="noRunningScriptsLabel">
-   <property name="geometry">
-    <rect>
-     <x>36</x>
-     <y>110</y>
-     <width>271</width>
-     <height>51</height>
-    </rect>
-   </property>
-   <property name="styleSheet">
-    <string notr="true">font: 14pt;</string>
-   </property>
-   <property name="text">
-    <string>There are no scripts currently running.</string>
-   </property>
-   <property name="alignment">
-    <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
-   </property>
-  </widget>
-  <widget class="QWidget" name="recentlyLoadedScriptsTableWidget" native="true">
-   <property name="geometry">
-    <rect>
-     <x>30</x>
-     <y>340</y>
-     <width>272</width>
-     <height>280</height>
-    </rect>
-   </property>
-   <property name="styleSheet">
-    <string notr="true">background: transparent;
-font-size: 14pt;</string>
-   </property>
-  </widget>
-  <widget class="QWidget" name="runningScriptsTableWidget" native="true">
-   <property name="geometry">
-    <rect>
-     <x>30</x>
-     <y>128</y>
-     <width>272</width>
-     <height>161</height>
-    </rect>
-   </property>
-   <property name="styleSheet">
-    <string notr="true">background: transparent;
-font-size: 14pt;</string>
-   </property>
-  </widget>
-  <widget class="QPushButton" name="loadScriptButton">
-   <property name="geometry">
-    <rect>
-     <x>36</x>
-     <y>70</y>
-     <width>111</width>
-     <height>35</height>
-    </rect>
-   </property>
-   <property name="cursor">
-    <cursorShape>PointingHandCursor</cursorShape>
-   </property>
-   <property name="styleSheet">
-    <string notr="true">background: #0e7077;
+           </property>
+           <property name="text">
+            <string>Stop all</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <spacer name="horizontalSpacer_2">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+        </layout>
+       </widget>
+      </item>
+      <item>
+       <spacer name="verticalSpacer_4">
+        <property name="orientation">
+         <enum>Qt::Vertical</enum>
+        </property>
+        <property name="sizeType">
+         <enum>QSizePolicy::Fixed</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>20</width>
+          <height>8</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+      <item>
+       <widget class="QLabel" name="noRunningScriptsLabel">
+        <property name="styleSheet">
+         <string notr="true">font: 14px;</string>
+        </property>
+        <property name="text">
+         <string>There are no scripts currently running.</string>
+        </property>
+        <property name="alignment">
+         <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <spacer name="verticalSpacer_2">
+        <property name="orientation">
+         <enum>Qt::Vertical</enum>
+        </property>
+        <property name="sizeType">
+         <enum>QSizePolicy::Fixed</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>20</width>
+          <height>10</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+      <item>
+       <widget class="QScrollArea" name="runningScriptsList">
+        <property name="font">
+         <font>
+          <family>Helvetica,Arial,sans-serif</family>
+          <pointsize>14</pointsize>
+         </font>
+        </property>
+        <property name="layoutDirection">
+         <enum>Qt::LeftToRight</enum>
+        </property>
+        <property name="styleSheet">
+         <string notr="true">margin: 0;</string>
+        </property>
+        <property name="frameShape">
+         <enum>QFrame::NoFrame</enum>
+        </property>
+        <property name="lineWidth">
+         <number>0</number>
+        </property>
+        <property name="verticalScrollBarPolicy">
+         <enum>Qt::ScrollBarAlwaysOn</enum>
+        </property>
+        <property name="horizontalScrollBarPolicy">
+         <enum>Qt::ScrollBarAlwaysOff</enum>
+        </property>
+        <property name="widgetResizable">
+         <bool>true</bool>
+        </property>
+        <property name="alignment">
+         <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+        </property>
+        <widget class="QWidget" name="scrollAreaWidgetContents">
+         <property name="geometry">
+          <rect>
+           <x>0</x>
+           <y>0</y>
+           <width>269</width>
+           <height>16</height>
+          </rect>
+         </property>
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="styleSheet">
+          <string notr="true">font-size: 14px;</string>
+         </property>
+         <layout class="QVBoxLayout" name="verticalLayout_5">
+          <property name="spacing">
+           <number>0</number>
+          </property>
+          <property name="leftMargin">
+           <number>0</number>
+          </property>
+          <property name="topMargin">
+           <number>0</number>
+          </property>
+          <property name="rightMargin">
+           <number>0</number>
+          </property>
+          <property name="bottomMargin">
+           <number>0</number>
+          </property>
+         </layout>
+        </widget>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QWidget" name="recentlyLoadedScriptsArea" native="true">
+     <property name="enabled">
+      <bool>true</bool>
+     </property>
+     <property name="minimumSize">
+      <size>
+       <width>0</width>
+       <height>100</height>
+      </size>
+     </property>
+     <property name="maximumSize">
+      <size>
+       <width>16777215</width>
+       <height>16777215</height>
+      </size>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_3">
+      <property name="spacing">
+       <number>0</number>
+      </property>
+      <property name="leftMargin">
+       <number>0</number>
+      </property>
+      <property name="topMargin">
+       <number>0</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+      <item>
+       <widget class="QLabel" name="recentlyLoadedLabel">
+        <property name="styleSheet">
+         <string notr="true">color: #0e7077;
+font: bold 16px;</string>
+        </property>
+        <property name="text">
+         <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Recently loaded&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="noRecentlyLoadedLabel">
+        <property name="styleSheet">
+         <string notr="true">font: 14px;</string>
+        </property>
+        <property name="text">
+         <string>There are no recently loaded scripts.</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QWidget" name="recentlyLoadedScriptsTableWidget" native="true">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+          <horstretch>0</horstretch>
+          <verstretch>1</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="minimumSize">
+         <size>
+          <width>284</width>
+          <height>0</height>
+         </size>
+        </property>
+        <property name="styleSheet">
+         <string notr="true">background: transparent;
+font-size: 14px;</string>
+        </property>
+        <zorder>runningScriptsList</zorder>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QWidget" name="quickLoadArea" native="true">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+       <horstretch>0</horstretch>
+       <verstretch>2</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="minimumSize">
+      <size>
+       <width>0</width>
+       <height>300</height>
+      </size>
+     </property>
+     <property name="styleSheet">
+      <string notr="true"/>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout">
+      <property name="spacing">
+       <number>0</number>
+      </property>
+      <property name="leftMargin">
+       <number>0</number>
+      </property>
+      <property name="topMargin">
+       <number>0</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+      <item>
+       <widget class="QWidget" name="widget" native="true">
+        <layout class="QHBoxLayout" name="horizontalLayout_2">
+         <property name="spacing">
+          <number>0</number>
+         </property>
+         <property name="leftMargin">
+          <number>0</number>
+         </property>
+         <property name="topMargin">
+          <number>0</number>
+         </property>
+         <property name="rightMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>15</number>
+         </property>
+         <item>
+          <widget class="QLabel" name="label">
+           <property name="styleSheet">
+            <string notr="true">color: #0e7077;
+font: bold 16px;</string>
+           </property>
+           <property name="text">
+            <string>Scripts</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <spacer name="horizontalSpacer_3">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+         <item>
+          <widget class="QPushButton" name="loadScriptButton">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="minimumSize">
+            <size>
+             <width>111</width>
+             <height>35</height>
+            </size>
+           </property>
+           <property name="cursor">
+            <cursorShape>PointingHandCursor</cursorShape>
+           </property>
+           <property name="styleSheet">
+            <string notr="true">background: #0e7077;
 color: #fff;
 border-radius: 4px;
-font: bold 14pt;
+font: bold 14px;
 padding-top: 3px;</string>
-   </property>
-   <property name="text">
-    <string>Load script</string>
-   </property>
-  </widget>
-  <zorder>widgetTitle</zorder>
-  <zorder>currentlyRunningLabel</zorder>
-  <zorder>recentlyLoadedLabel</zorder>
-  <zorder>recentlyLoadedInstruction</zorder>
-  <zorder>hideWidgetButton</zorder>
-  <zorder>recentlyLoadedScriptsTableWidget</zorder>
-  <zorder>runningScriptsTableWidget</zorder>
-  <zorder>noRunningScriptsLabel</zorder>
-  <zorder>reloadAllButton</zorder>
-  <zorder>stopAllButton</zorder>
-  <zorder>loadScriptButton</zorder>
+           </property>
+           <property name="text">
+            <string>Load script</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLineEdit" name="filterLineEdit">
+        <property name="styleSheet">
+         <string notr="true">border: 1px solid rgb(128, 128, 128);
+border-radius: 2px;
+padding: 4px;
+background-color: white;</string>
+        </property>
+        <property name="text">
+         <string/>
+        </property>
+        <property name="placeholderText">
+         <string>filter</string>
+        </property>
+        <property name="clearButtonEnabled">
+         <bool>true</bool>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <spacer name="verticalSpacer">
+        <property name="orientation">
+         <enum>Qt::Vertical</enum>
+        </property>
+        <property name="sizeType">
+         <enum>QSizePolicy::Fixed</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>20</width>
+          <height>6</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+      <item>
+       <widget class="QListView" name="scriptListView">
+        <property name="styleSheet">
+         <string notr="true">QListView {
+	border: 1px solid rgb(128, 128, 128);
+	border-radius: 2px;
+}
+QListView::item {
+	padding-top: 2px;
+	padding-bottom: 2px;
+}</string>
+        </property>
+        <property name="frameShape">
+         <enum>QFrame::Box</enum>
+        </property>
+        <property name="frameShadow">
+         <enum>QFrame::Plain</enum>
+        </property>
+        <property name="verticalScrollBarPolicy">
+         <enum>Qt::ScrollBarAlwaysOff</enum>
+        </property>
+        <property name="horizontalScrollBarPolicy">
+         <enum>Qt::ScrollBarAsNeeded</enum>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+  </layout>
  </widget>
  <resources/>
  <connections/>
diff --git a/interface/ui/scriptEditorWindow.ui b/interface/ui/scriptEditorWindow.ui
index 9103fc1f57..0379f51e97 100644
--- a/interface/ui/scriptEditorWindow.ui
+++ b/interface/ui/scriptEditorWindow.ui
@@ -33,7 +33,7 @@
     <number>0</number>
    </property>
    <item>
-    <layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,0">
+    <layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,0,0">
      <property name="spacing">
       <number>3</number>
      </property>
@@ -185,6 +185,16 @@
        </property>
       </spacer>
      </item>
+     <item>
+      <widget class="QCheckBox" name="autoReloadCheckBox">
+       <property name="styleSheet">
+        <string notr="true">font: 13px &quot;Helvetica&quot;,&quot;Arial&quot;,&quot;sans-serif&quot;;</string>
+       </property>
+       <property name="text">
+        <string>Automatically reload externally changed files</string>
+       </property>
+      </widget>
+     </item>
     </layout>
    </item>
    <item>
diff --git a/interface/ui/shareSnapshot.ui b/interface/ui/shareSnapshot.ui
index df7fc4939f..19e0772f13 100644
--- a/interface/ui/shareSnapshot.ui
+++ b/interface/ui/shareSnapshot.ui
@@ -277,7 +277,7 @@ padding-left:20px;</string>
           <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
 &lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
 p, li { white-space: pre-wrap; }
-&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Helvetica'; font-size:14pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Helvetica'; font-size:14px; font-weight:400; font-style:normal;&quot;&gt;
 &lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
          </property>
         </widget>
diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp
index 129dc47bd0..e5c1230832 100644
--- a/libraries/audio/src/AudioInjector.cpp
+++ b/libraries/audio/src/AudioInjector.cpp
@@ -61,6 +61,11 @@ void AudioInjector::injectAudio() {
         QByteArray injectAudioPacket = byteArrayWithPopulatedHeader(PacketTypeInjectAudio);
         QDataStream packetStream(&injectAudioPacket, QIODevice::Append);
         
+        // pack some placeholder sequence number for now
+        int numPreSequenceNumberBytes = injectAudioPacket.size();
+        packetStream << (quint16)0;
+
+        // pack stream identifier (a generated UUID)
         packetStream << QUuid::createUuid();
         
         // pack the flag for loopback
@@ -91,6 +96,7 @@ void AudioInjector::injectAudio() {
         bool shouldLoop = _options.getLoop();
         
         // loop to send off our audio in NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL byte chunks
+        quint16 outgoingInjectedAudioSequenceNumber = 0;
         while (currentSendPosition < soundByteArray.size() && !_shouldStop) {
             
             int bytesToCopy = std::min(NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL,
@@ -98,6 +104,9 @@ void AudioInjector::injectAudio() {
             
             // resize the QByteArray to the right size
             injectAudioPacket.resize(numPreAudioDataBytes + bytesToCopy);
+
+            // pack the sequence number
+            memcpy(injectAudioPacket.data() + numPreSequenceNumberBytes, &outgoingInjectedAudioSequenceNumber, sizeof(quint16));
             
             // copy the next NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL bytes to the packet
             memcpy(injectAudioPacket.data() + numPreAudioDataBytes, soundByteArray.data() + currentSendPosition, bytesToCopy);
@@ -107,6 +116,7 @@ void AudioInjector::injectAudio() {
             
             // send off this audio packet
             nodeList->writeDatagram(injectAudioPacket, audioMixer);
+            outgoingInjectedAudioSequenceNumber++;
             
             currentSendPosition += bytesToCopy;
             
diff --git a/libraries/audio/src/AudioRingBuffer.cpp b/libraries/audio/src/AudioRingBuffer.cpp
index 2101fcb9cd..6ae3b19541 100644
--- a/libraries/audio/src/AudioRingBuffer.cpp
+++ b/libraries/audio/src/AudioRingBuffer.cpp
@@ -16,12 +16,14 @@
 #include <QtCore/QDebug>
 
 #include "PacketHeaders.h"
-
 #include "AudioRingBuffer.h"
 
+
 AudioRingBuffer::AudioRingBuffer(int numFrameSamples, bool randomAccessMode) :
     NodeData(),
+    _overflowCount(0),
     _sampleCapacity(numFrameSamples * RING_BUFFER_LENGTH_FRAMES),
+    _isFull(false),
     _numFrameSamples(numFrameSamples),
     _isStarved(true),
     _hasStarted(false),
@@ -63,8 +65,9 @@ void AudioRingBuffer::resizeForFrameSize(qint64 numFrameSamples) {
 }
 
 int AudioRingBuffer::parseData(const QByteArray& packet) {
-    int numBytesPacketHeader = numBytesForPacketHeader(packet);
-    return writeData(packet.data() + numBytesPacketHeader, packet.size() - numBytesPacketHeader);
+    // skip packet header and sequence number
+    int numBytesBeforeAudioData = numBytesForPacketHeader(packet) + sizeof(quint16);
+    return writeData(packet.data() + numBytesBeforeAudioData, packet.size() - numBytesBeforeAudioData);
 }
 
 qint64 AudioRingBuffer::readSamples(int16_t* destination, qint64 maxSamples) {
@@ -108,6 +111,9 @@ qint64 AudioRingBuffer::readData(char *data, qint64 maxSize) {
 
     // push the position of _nextOutput by the number of samples read
     _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numReadSamples);
+    if (numReadSamples > 0) {
+        _isFull = false;
+    }
 
     return numReadSamples * sizeof(int16_t);
 }
@@ -119,22 +125,17 @@ qint64 AudioRingBuffer::writeSamples(const int16_t* source, qint64 maxSamples) {
 qint64 AudioRingBuffer::writeData(const char* data, qint64 maxSize) {
     // make sure we have enough bytes left for this to be the right amount of audio
     // otherwise we should not copy that data, and leave the buffer pointers where they are
-
     int samplesToCopy = std::min((quint64)(maxSize / sizeof(int16_t)), (quint64)_sampleCapacity);
-
-    std::less<int16_t*> less;
-    std::less_equal<int16_t*> lessEqual;
-
-    if (_hasStarted
-        && (less(_endOfLastWrite, _nextOutput)
-            && lessEqual(_nextOutput, shiftedPositionAccomodatingWrap(_endOfLastWrite, samplesToCopy)))) {
-        // this read will cross the next output, so call us starved and reset the buffer
-        qDebug() << "Filled the ring buffer. Resetting.";
-        _endOfLastWrite = _buffer;
-        _nextOutput = _buffer;
-        _isStarved = true;
+    
+    int samplesRoomFor = _sampleCapacity - samplesAvailable();
+    if (samplesToCopy > samplesRoomFor) {
+        // there's not enough room for this write.  erase old data to make room for this new data
+        int samplesToDelete = samplesToCopy - samplesRoomFor;
+        _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, samplesToDelete);
+        _overflowCount++;
+        qDebug() << "Overflowed ring buffer! Overwriting old data";
     }
-
+    
     if (_endOfLastWrite + samplesToCopy <= _buffer + _sampleCapacity) {
         memcpy(_endOfLastWrite, data, samplesToCopy * sizeof(int16_t));
     } else {
@@ -144,6 +145,9 @@ qint64 AudioRingBuffer::writeData(const char* data, qint64 maxSize) {
     }
 
     _endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, samplesToCopy);
+    if (samplesToCopy > 0 && _endOfLastWrite == _nextOutput) {
+        _isFull = true;
+    }
 
     return samplesToCopy * sizeof(int16_t);
 }
@@ -157,36 +161,51 @@ const int16_t& AudioRingBuffer::operator[] (const int index) const {
 }
 
 void AudioRingBuffer::shiftReadPosition(unsigned int numSamples) {
-    _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numSamples);
+    if (numSamples > 0) {
+        _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numSamples);
+        _isFull = false;
+    }
 }
 
 unsigned int AudioRingBuffer::samplesAvailable() const {
     if (!_endOfLastWrite) {
         return 0;
-    } else {
-        int sampleDifference = _endOfLastWrite - _nextOutput;
-
-        if (sampleDifference < 0) {
-            sampleDifference += _sampleCapacity;
-        }
-
-        return sampleDifference;
     }
+    if (_isFull) {
+        return _sampleCapacity;
+    }
+
+    int sampleDifference = _endOfLastWrite - _nextOutput;
+    if (sampleDifference < 0) {
+        sampleDifference += _sampleCapacity;
+    }
+    return sampleDifference;
 }
 
-void AudioRingBuffer::addSilentFrame(int numSilentSamples) {
+int AudioRingBuffer::addSilentFrame(int numSilentSamples) {
+
+    int samplesRoomFor = _sampleCapacity - samplesAvailable();
+    if (numSilentSamples > samplesRoomFor) {
+        // there's not enough room for this write. write as many silent samples as we have room for
+        numSilentSamples = samplesRoomFor;
+        qDebug() << "Dropping some silent samples to prevent ring buffer overflow";
+    }
+
     // memset zeroes into the buffer, accomodate a wrap around the end
     // push the _endOfLastWrite to the correct spot
     if (_endOfLastWrite + numSilentSamples <= _buffer + _sampleCapacity) {
         memset(_endOfLastWrite, 0, numSilentSamples * sizeof(int16_t));
-        _endOfLastWrite += numSilentSamples;
     } else {
         int numSamplesToEnd = (_buffer + _sampleCapacity) - _endOfLastWrite;
         memset(_endOfLastWrite, 0, numSamplesToEnd * sizeof(int16_t));
         memset(_buffer, 0, (numSilentSamples - numSamplesToEnd) * sizeof(int16_t));
-        
-        _endOfLastWrite = _buffer + (numSilentSamples - numSamplesToEnd);
     }
+    _endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, numSilentSamples);
+    if (numSilentSamples > 0 && _nextOutput == _endOfLastWrite) {
+        _isFull = true;
+    }
+
+    return numSilentSamples * sizeof(int16_t);
 }
 
 bool AudioRingBuffer::isNotStarvedOrHasMinimumSamples(unsigned int numRequiredSamples) const {
diff --git a/libraries/audio/src/AudioRingBuffer.h b/libraries/audio/src/AudioRingBuffer.h
index 04cc67c8ac..8d19f9c0bb 100644
--- a/libraries/audio/src/AudioRingBuffer.h
+++ b/libraries/audio/src/AudioRingBuffer.h
@@ -71,17 +71,21 @@ public:
     bool isStarved() const { return _isStarved; }
     void setIsStarved(bool isStarved) { _isStarved = isStarved; }
     
+    int getOverflowCount() const { return _overflowCount; } /// how many times has the ring buffer has overwritten old data
     bool hasStarted() const { return _hasStarted; }
     
-    void addSilentFrame(int numSilentSamples);
+    int addSilentFrame(int numSilentSamples);
 protected:
     // disallow copying of AudioRingBuffer objects
     AudioRingBuffer(const AudioRingBuffer&);
     AudioRingBuffer& operator= (const AudioRingBuffer&);
     
     int16_t* shiftedPositionAccomodatingWrap(int16_t* position, int numSamplesShift) const;
+
+    int _overflowCount; /// how many times has the ring buffer has overwritten old data
     
     int _sampleCapacity;
+    bool _isFull;
     int _numFrameSamples;
     int16_t* _nextOutput;
     int16_t* _endOfLastWrite;
diff --git a/libraries/audio/src/AudioStreamStats.h b/libraries/audio/src/AudioStreamStats.h
new file mode 100644
index 0000000000..004d697fcf
--- /dev/null
+++ b/libraries/audio/src/AudioStreamStats.h
@@ -0,0 +1,46 @@
+//
+//  AudioStreamStats.h
+//  libraries/audio/src
+//
+//  Created by Yixin Wang on 6/25/2014
+//  Copyright 2014 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
+//
+
+#ifndef hifi_AudioStreamStats_h
+#define hifi_AudioStreamStats_h
+
+#include "PositionalAudioRingBuffer.h"
+
+class AudioStreamStats {
+public:
+    AudioStreamStats()
+        : _streamType(PositionalAudioRingBuffer::Microphone),
+        _streamIdentifier(),
+        _jitterBufferFrames(0),
+        _packetsReceived(0),
+        _packetsUnreasonable(0),
+        _packetsEarly(0),
+        _packetsLate(0),
+        _packetsLost(0),
+        _packetsRecovered(0),
+        _packetsDuplicate(0)
+    {}
+
+    PositionalAudioRingBuffer::Type _streamType;
+    QUuid _streamIdentifier;
+
+    quint16 _jitterBufferFrames;
+
+    quint32 _packetsReceived;
+    quint32 _packetsUnreasonable;
+    quint32 _packetsEarly;
+    quint32 _packetsLate;
+    quint32 _packetsLost;
+    quint32 _packetsRecovered;
+    quint32 _packetsDuplicate;
+};
+
+#endif  // hifi_AudioStreamStats_h
diff --git a/libraries/audio/src/InjectedAudioRingBuffer.cpp b/libraries/audio/src/InjectedAudioRingBuffer.cpp
index 2658b4c336..0d7cea356b 100644
--- a/libraries/audio/src/InjectedAudioRingBuffer.cpp
+++ b/libraries/audio/src/InjectedAudioRingBuffer.cpp
@@ -19,8 +19,8 @@
 
 #include "InjectedAudioRingBuffer.h"
 
-InjectedAudioRingBuffer::InjectedAudioRingBuffer(const QUuid& streamIdentifier) :
-    PositionalAudioRingBuffer(PositionalAudioRingBuffer::Injector),
+InjectedAudioRingBuffer::InjectedAudioRingBuffer(const QUuid& streamIdentifier, bool dynamicJitterBuffer) :
+    PositionalAudioRingBuffer(PositionalAudioRingBuffer::Injector, /* isStereo=*/ false , dynamicJitterBuffer),
     _streamIdentifier(streamIdentifier),
     _radius(0.0f),
     _attenuationRatio(0)
@@ -31,10 +31,16 @@ InjectedAudioRingBuffer::InjectedAudioRingBuffer(const QUuid& streamIdentifier)
 const uchar MAX_INJECTOR_VOLUME = 255;
 
 int InjectedAudioRingBuffer::parseData(const QByteArray& packet) {
+    _interframeTimeGapStats.frameReceived();
+    updateDesiredJitterBufferFrames();
+
     // setup a data stream to read from this packet
     QDataStream packetStream(packet);
     packetStream.skipRawData(numBytesForPacketHeader(packet));
     
+    // push past the sequence number
+    packetStream.skipRawData(sizeof(quint16));
+
     // push past the stream identifier
     packetStream.skipRawData(NUM_BYTES_RFC4122_UUID);
     
diff --git a/libraries/audio/src/InjectedAudioRingBuffer.h b/libraries/audio/src/InjectedAudioRingBuffer.h
index fd766e2848..4e3fea672b 100644
--- a/libraries/audio/src/InjectedAudioRingBuffer.h
+++ b/libraries/audio/src/InjectedAudioRingBuffer.h
@@ -18,7 +18,7 @@
 
 class InjectedAudioRingBuffer : public PositionalAudioRingBuffer {
 public:
-    InjectedAudioRingBuffer(const QUuid& streamIdentifier = QUuid());
+    InjectedAudioRingBuffer(const QUuid& streamIdentifier = QUuid(), bool dynamicJitterBuffer = false);
     
     int parseData(const QByteArray& packet);
     
diff --git a/libraries/audio/src/PositionalAudioRingBuffer.cpp b/libraries/audio/src/PositionalAudioRingBuffer.cpp
index 1cc4147175..546ed97fe2 100644
--- a/libraries/audio/src/PositionalAudioRingBuffer.cpp
+++ b/libraries/audio/src/PositionalAudioRingBuffer.cpp
@@ -19,8 +19,75 @@
 #include <UUID.h>
 
 #include "PositionalAudioRingBuffer.h"
+#include "SharedUtil.h"
 
-PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer::Type type, bool isStereo) :
+InterframeTimeGapStats::InterframeTimeGapStats()
+    : _lastFrameReceivedTime(0),
+    _numSamplesInCurrentInterval(0),
+    _currentIntervalMaxGap(0),
+    _newestIntervalMaxGapAt(0),
+    _windowMaxGap(0),
+    _newWindowMaxGapAvailable(false)
+{
+    memset(_intervalMaxGaps, 0, TIME_GAP_NUM_INTERVALS_IN_WINDOW * sizeof(quint64));
+}
+
+void InterframeTimeGapStats::frameReceived() {
+    quint64 now = usecTimestampNow();
+
+    // make sure this isn't the first time frameReceived() is called so can actually calculate a gap.
+    if (_lastFrameReceivedTime != 0) {
+        quint64 gap = now - _lastFrameReceivedTime;
+
+        // update the current interval max
+        if (gap > _currentIntervalMaxGap) {
+            _currentIntervalMaxGap = gap;
+
+            // keep the window max gap at least as large as the current interval max
+            // this allows the window max gap to respond immediately to a sudden spike in gap times
+            // also, this prevents the window max gap from staying at 0 until the first interval of samples filled up
+            if (_currentIntervalMaxGap > _windowMaxGap) {
+                _windowMaxGap = _currentIntervalMaxGap;
+                _newWindowMaxGapAvailable = true;
+            }
+        }
+        _numSamplesInCurrentInterval++;
+
+        // if the current interval of samples is now full, record it in our interval maxes
+        if (_numSamplesInCurrentInterval == TIME_GAP_NUM_SAMPLES_IN_INTERVAL) {
+
+            // find location to insert this interval's max (increment index cyclically)
+            _newestIntervalMaxGapAt = _newestIntervalMaxGapAt == TIME_GAP_NUM_INTERVALS_IN_WINDOW - 1 ? 0 : _newestIntervalMaxGapAt + 1;
+
+            // record the current interval's max gap as the newest
+            _intervalMaxGaps[_newestIntervalMaxGapAt] = _currentIntervalMaxGap;
+
+            // update the window max gap, which is the max out of all the past intervals' max gaps
+            _windowMaxGap = 0;
+            for (int i = 0; i < TIME_GAP_NUM_INTERVALS_IN_WINDOW; i++) {
+                if (_intervalMaxGaps[i] > _windowMaxGap) {
+                    _windowMaxGap = _intervalMaxGaps[i];
+                }
+            }
+            _newWindowMaxGapAvailable = true;
+
+            // reset the current interval
+            _numSamplesInCurrentInterval = 0;
+            _currentIntervalMaxGap = 0;
+        }
+    }
+    _lastFrameReceivedTime = now;
+}
+
+quint64 InterframeTimeGapStats::getWindowMaxGap() {
+    _newWindowMaxGapAvailable = false;
+    return _windowMaxGap;
+}
+
+
+PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer::Type type, 
+        bool isStereo, bool dynamicJitterBuffers) :
+        
     AudioRingBuffer(isStereo ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL),
     _type(type),
     _position(0.0f, 0.0f, 0.0f),
@@ -29,15 +96,20 @@ PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer::
     _shouldLoopbackForNode(false),
     _shouldOutputStarveDebug(true),
     _isStereo(isStereo),
-    _listenerUnattenuatedZone(NULL)
+    _listenerUnattenuatedZone(NULL),
+    _desiredJitterBufferFrames(1),
+    _currentJitterBufferFrames(0),
+    _dynamicJitterBuffers(dynamicJitterBuffers)
 {
-
 }
 
 int PositionalAudioRingBuffer::parseData(const QByteArray& packet) {
     
     // skip the packet header (includes the source UUID)
     int readBytes = numBytesForPacketHeader(packet);
+
+    // skip the sequence number
+    readBytes += sizeof(quint16);
     
     // hop over the channel flag that has already been read in AudioMixerClientData
     readBytes += sizeof(quint8);
@@ -53,14 +125,35 @@ int PositionalAudioRingBuffer::parseData(const QByteArray& packet) {
         
         readBytes += sizeof(int16_t);
         
+        // NOTE: fixes a bug in old clients that would send garbage for their number of silentSamples
+        numSilentSamples = getSamplesPerFrame();
+        
         if (numSilentSamples > 0) {
-            addSilentFrame(numSilentSamples);
+            if (_currentJitterBufferFrames > _desiredJitterBufferFrames) {
+                // our current jitter buffer size exceeds its desired value, so ignore some silent
+                // frames to get that size as close to desired as possible
+                int samplesPerFrame = getSamplesPerFrame();
+                int numSilentFrames = numSilentSamples / samplesPerFrame;
+                int numFramesToDropDesired = _currentJitterBufferFrames - _desiredJitterBufferFrames;
+
+                if (numSilentFrames > numFramesToDropDesired) {
+                    // we have more than enough frames to drop to get the jitter buffer to its desired length
+                    int numSilentFramesToAdd = numSilentFrames - numFramesToDropDesired;
+                    addSilentFrame(numSilentFramesToAdd * samplesPerFrame);
+                    _currentJitterBufferFrames = _desiredJitterBufferFrames;
+
+                } else {
+                    // we need to drop all frames to get the jitter buffer close as possible to its desired length
+                    _currentJitterBufferFrames -= numSilentFrames;
+                }
+            } else {
+                addSilentFrame(numSilentSamples);
+            }
         }
     } else {
         // there is audio data to read
         readBytes += writeData(packet.data() + readBytes, packet.size() - readBytes);
     }
-    
     return readBytes;
 }
 
@@ -106,29 +199,72 @@ void PositionalAudioRingBuffer::updateNextOutputTrailingLoudness() {
     }
 }
 
-bool PositionalAudioRingBuffer::shouldBeAddedToMix(int numJitterBufferSamples) {
-    if (!isNotStarvedOrHasMinimumSamples(NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL + numJitterBufferSamples)) {
+bool PositionalAudioRingBuffer::shouldBeAddedToMix() {
+    int samplesPerFrame = getSamplesPerFrame();
+    int desiredJitterBufferSamples = _desiredJitterBufferFrames * samplesPerFrame;
+    
+    if (!isNotStarvedOrHasMinimumSamples(samplesPerFrame + desiredJitterBufferSamples)) {
+        // if the buffer was starved, allow it to accrue at least the desired number of
+        // jitter buffer frames before we start taking frames from it for mixing
+        
         if (_shouldOutputStarveDebug) {
             _shouldOutputStarveDebug = false;
         }
-        
-        return false;
-    } else if (samplesAvailable() < NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL) {
+
+        return  false;
+    } else if (samplesAvailable() < (unsigned int)samplesPerFrame) { 
+        // if the buffer doesn't have a full frame of samples to take for mixing, it is starved
         _isStarved = true;
         
+        // set to 0 to indicate the jitter buffer is starved
+        _currentJitterBufferFrames = 0;
+        
         // reset our _shouldOutputStarveDebug to true so the next is printed
         _shouldOutputStarveDebug = true;
-        
+
         return false;
-    } else {
-        // good buffer, add this to the mix
+    }
+    
+    // good buffer, add this to the mix
+    if (_isStarved) {
+        // if this buffer has just finished replenishing after being starved, the number of frames in it now
+        // minus one (since a frame will be read immediately after this) is the length of the jitter buffer
+        _currentJitterBufferFrames = samplesAvailable() / samplesPerFrame - 1;
         _isStarved = false;
-
-        // since we've read data from ring buffer at least once - we've started
-        _hasStarted = true;
-
-        return true;
     }
 
-    return false;
+    // since we've read data from ring buffer at least once - we've started
+    _hasStarted = true;
+
+    return true;
+}
+
+int PositionalAudioRingBuffer::getCalculatedDesiredJitterBufferFrames() const {
+    int calculatedDesiredJitterBufferFrames = 1;
+    const float USECS_PER_FRAME = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * USECS_PER_SECOND / (float)SAMPLE_RATE;
+     
+    calculatedDesiredJitterBufferFrames = ceilf((float)_interframeTimeGapStats.peekWindowMaxGap() / USECS_PER_FRAME);
+    if (calculatedDesiredJitterBufferFrames < 1) {
+        calculatedDesiredJitterBufferFrames = 1;
+    }
+    return calculatedDesiredJitterBufferFrames;
+}
+
+void PositionalAudioRingBuffer::updateDesiredJitterBufferFrames() {
+    if (_interframeTimeGapStats.hasNewWindowMaxGapAvailable()) {
+        if (!_dynamicJitterBuffers) {
+            _desiredJitterBufferFrames = 1; // HACK to see if this fixes the audio silence
+        } else {
+            const float USECS_PER_FRAME = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * USECS_PER_SECOND / (float)SAMPLE_RATE;
+            
+            _desiredJitterBufferFrames = ceilf((float)_interframeTimeGapStats.getWindowMaxGap() / USECS_PER_FRAME);
+            if (_desiredJitterBufferFrames < 1) {
+                _desiredJitterBufferFrames = 1;
+            }
+            const int maxDesired = RING_BUFFER_LENGTH_FRAMES - 1;
+            if (_desiredJitterBufferFrames > maxDesired) {
+                _desiredJitterBufferFrames = maxDesired;
+            }
+        }
+    }
 }
diff --git a/libraries/audio/src/PositionalAudioRingBuffer.h b/libraries/audio/src/PositionalAudioRingBuffer.h
index 00362c245a..b204dc766b 100644
--- a/libraries/audio/src/PositionalAudioRingBuffer.h
+++ b/libraries/audio/src/PositionalAudioRingBuffer.h
@@ -18,6 +18,31 @@
 
 #include "AudioRingBuffer.h"
 
+// this means that every 500 samples, the max for the past 10*500 samples will be calculated
+const int TIME_GAP_NUM_SAMPLES_IN_INTERVAL = 500;
+const int TIME_GAP_NUM_INTERVALS_IN_WINDOW = 10;
+
+// class used to track time between incoming frames for the purpose of varying the jitter buffer length
+class InterframeTimeGapStats {
+public:
+    InterframeTimeGapStats();
+
+    void frameReceived();
+    bool hasNewWindowMaxGapAvailable() const { return _newWindowMaxGapAvailable; }
+    quint64 peekWindowMaxGap() const { return _windowMaxGap; }
+    quint64 getWindowMaxGap();
+
+private:
+    quint64 _lastFrameReceivedTime;
+
+    int _numSamplesInCurrentInterval;
+    quint64 _currentIntervalMaxGap;
+    quint64 _intervalMaxGaps[TIME_GAP_NUM_INTERVALS_IN_WINDOW];
+    int _newestIntervalMaxGapAt;
+    quint64 _windowMaxGap;
+    bool _newWindowMaxGapAvailable;
+};
+
 class PositionalAudioRingBuffer : public AudioRingBuffer {
 public:
     enum Type {
@@ -25,7 +50,7 @@ public:
         Injector
     };
     
-    PositionalAudioRingBuffer(PositionalAudioRingBuffer::Type type, bool isStereo = false);
+    PositionalAudioRingBuffer(PositionalAudioRingBuffer::Type type, bool isStereo = false, bool dynamicJitterBuffers = false);
     
     int parseData(const QByteArray& packet);
     int parsePositionalData(const QByteArray& positionalByteArray);
@@ -34,7 +59,7 @@ public:
     void updateNextOutputTrailingLoudness();
     float getNextOutputTrailingLoudness() const { return _nextOutputTrailingLoudness; }
     
-    bool shouldBeAddedToMix(int numJitterBufferSamples);
+    bool shouldBeAddedToMix();
     
     bool willBeAddedToMix() const { return _willBeAddedToMix; }
     void setWillBeAddedToMix(bool willBeAddedToMix) { _willBeAddedToMix = willBeAddedToMix; }
@@ -50,10 +75,18 @@ public:
     AABox* getListenerUnattenuatedZone() const { return _listenerUnattenuatedZone; }
     void setListenerUnattenuatedZone(AABox* listenerUnattenuatedZone) { _listenerUnattenuatedZone = listenerUnattenuatedZone; }
     
+    int getSamplesPerFrame() const { return _isStereo ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; }
+
+    int getCalculatedDesiredJitterBufferFrames() const; /// returns what we would calculate our desired as if asked
+    int getDesiredJitterBufferFrames() const { return _desiredJitterBufferFrames; }
+    int getCurrentJitterBufferFrames() const { return _currentJitterBufferFrames; }
+
 protected:
     // disallow copying of PositionalAudioRingBuffer objects
     PositionalAudioRingBuffer(const PositionalAudioRingBuffer&);
     PositionalAudioRingBuffer& operator= (const PositionalAudioRingBuffer&);
+
+    void updateDesiredJitterBufferFrames();
     
     PositionalAudioRingBuffer::Type _type;
     glm::vec3 _position;
@@ -65,6 +98,11 @@ protected:
     
     float _nextOutputTrailingLoudness;
     AABox* _listenerUnattenuatedZone;
+
+    InterframeTimeGapStats _interframeTimeGapStats;
+    int _desiredJitterBufferFrames;
+    int _currentJitterBufferFrames;
+    bool _dynamicJitterBuffers;
 };
 
 #endif // hifi_PositionalAudioRingBuffer_h
diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h
index 8f658678b5..4c7136fd0a 100755
--- a/libraries/avatars/src/AvatarData.h
+++ b/libraries/avatars/src/AvatarData.h
@@ -223,7 +223,7 @@ public:
 
     virtual const glm::vec3& getVelocity() const { return vec3Zero; }
 
-    virtual bool findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions) {
+    virtual bool findSphereCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions) {
         return false;
     }
 
diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp
index 9aeb81a2a3..56fb566d6a 100644
--- a/libraries/fbx/src/FBXReader.cpp
+++ b/libraries/fbx/src/FBXReader.cpp
@@ -1717,7 +1717,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
                 glm::vec3 boneEnd = extractTranslation(transformJointToMesh);
                 glm::vec3 boneBegin = boneEnd;
                 glm::vec3 boneDirection;
-                float boneLength;
+                float boneLength = 0.0f;
                 if (joint.parentIndex != -1) {
                     boneBegin = extractTranslation(inverseModelTransform * geometry.joints[joint.parentIndex].bindTransform);
                     boneDirection = boneEnd - boneBegin;
@@ -1779,7 +1779,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
             glm::vec3 boneBegin = boneEnd;
 
             glm::vec3 boneDirection;
-            float boneLength;
+            float boneLength = 0.0f;
             if (joint.parentIndex != -1) {
                 boneBegin = extractTranslation(inverseModelTransform * geometry.joints[joint.parentIndex].bindTransform);
                 boneDirection = boneEnd - boneBegin;
@@ -1897,7 +1897,20 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
         }
         geometry.attachments.append(attachment);
     }
-
+    
+    // Add sitting points
+    QVariantHash sittingPoints = mapping.value("sit").toHash();
+    for (QVariantHash::const_iterator it = sittingPoints.constBegin(); it != sittingPoints.constEnd(); it++) {
+        SittingPoint sittingPoint;
+        sittingPoint.name = it.key();
+        
+        QVariantList properties = it->toList();
+        sittingPoint.position = parseVec3(properties.at(0).toString());
+        sittingPoint.rotation = glm::quat(glm::radians(parseVec3(properties.at(1).toString())));
+        
+        geometry.sittingPoints.append(sittingPoint);
+    }
+    
     return geometry;
 }
 
diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h
index 4c93f3dc5e..c336252574 100644
--- a/libraries/fbx/src/FBXReader.h
+++ b/libraries/fbx/src/FBXReader.h
@@ -182,6 +182,14 @@ public:
     glm::vec3 scale;
 };
 
+/// A point where an avatar can sit
+class SittingPoint {
+public:
+    QString name;
+    glm::vec3 position; // relative postion
+    glm::quat rotation; // relative orientation
+};
+
 /// A set of meshes extracted from an FBX document.
 class FBXGeometry {
 public:
@@ -209,6 +217,8 @@ public:
     
     glm::vec3 palmDirection;
     
+    QVector<SittingPoint> sittingPoints;
+    
     glm::vec3 neckPivot;
     
     Extents bindExtents;
diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp
index e7a7f41850..33ce298859 100644
--- a/libraries/metavoxels/src/AttributeRegistry.cpp
+++ b/libraries/metavoxels/src/AttributeRegistry.cpp
@@ -211,6 +211,11 @@ void Attribute::writeMetavoxelSubdivision(const MetavoxelNode& root, MetavoxelSt
     root.writeSubdivision(state);
 }
 
+bool Attribute::metavoxelRootsEqual(const MetavoxelNode& firstRoot, const MetavoxelNode& secondRoot,
+        const glm::vec3& minimum, float size, const MetavoxelLOD& lod) {
+    return firstRoot.deepEquals(this, secondRoot, minimum, size, lod);
+}
+
 FloatAttribute::FloatAttribute(const QString& name, float defaultValue) :
     SimpleInlineAttribute<float>(name, defaultValue) {
 }
@@ -449,6 +454,12 @@ void SharedObjectAttribute::write(Bitstream& out, void* value, bool isLeaf) cons
     }
 }
 
+bool SharedObjectAttribute::deepEqual(void* first, void* second) const {
+    SharedObjectPointer firstObject = decodeInline<SharedObjectPointer>(first);
+    SharedObjectPointer secondObject = decodeInline<SharedObjectPointer>(second);
+    return firstObject ? firstObject->equals(secondObject) : !secondObject;
+}
+
 bool SharedObjectAttribute::merge(void*& parent, void* children[], bool postRead) const {
     SharedObjectPointer firstChild = decodeInline<SharedObjectPointer>(children[0]);
     for (int i = 1; i < MERGE_COUNT; i++) {
@@ -489,6 +500,35 @@ MetavoxelNode* SharedObjectSetAttribute::createMetavoxelNode(
     return new MetavoxelNode(value, original);
 }
 
+static bool setsEqual(const SharedObjectSet& firstSet, const SharedObjectSet& secondSet) {
+    if (firstSet.size() != secondSet.size()) {
+        return false;
+    }
+    // some hackiness here: we assume that the local ids of the first set correspond to the remote ids of the second,
+    // so that this will work with the tests
+    foreach (const SharedObjectPointer& firstObject, firstSet) {
+        int id = firstObject->getID();
+        bool found = false;
+        foreach (const SharedObjectPointer& secondObject, secondSet) {
+            if (secondObject->getRemoteID() == id) {
+                if (!firstObject->equals(secondObject)) {
+                    return false;
+                }
+                found = true;
+                break;
+            }
+        }
+        if (!found) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool SharedObjectSetAttribute::deepEqual(void* first, void* second) const {
+    return setsEqual(decodeInline<SharedObjectSet>(first), decodeInline<SharedObjectSet>(second));
+}
+
 bool SharedObjectSetAttribute::merge(void*& parent, void* children[], bool postRead) const {
     for (int i = 0; i < MERGE_COUNT; i++) {
         if (!decodeInline<SharedObjectSet>(children[i]).isEmpty()) {
@@ -563,3 +603,12 @@ void SpannerSetAttribute::writeMetavoxelSubdivision(const MetavoxelNode& root, M
     state.stream << SharedObjectPointer();
 }
 
+bool SpannerSetAttribute::metavoxelRootsEqual(const MetavoxelNode& firstRoot, const MetavoxelNode& secondRoot,
+    const glm::vec3& minimum, float size, const MetavoxelLOD& lod) {
+    
+    SharedObjectSet firstSet;
+    firstRoot.getSpanners(this, minimum, size, lod, firstSet);
+    SharedObjectSet secondSet;
+    secondRoot.getSpanners(this, minimum, size, lod, secondSet);
+    return setsEqual(firstSet, secondSet);
+}
diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h
index 00d974b8b6..7dc2e110b8 100644
--- a/libraries/metavoxels/src/AttributeRegistry.h
+++ b/libraries/metavoxels/src/AttributeRegistry.h
@@ -27,6 +27,7 @@ class QScriptValue;
 
 class Attribute;
 class MetavoxelData;
+class MetavoxelLOD;
 class MetavoxelNode;
 class MetavoxelStreamState;
 
@@ -213,6 +214,11 @@ public:
 
     virtual bool equal(void* first, void* second) const = 0;
 
+    virtual bool deepEqual(void* first, void* second) const { return equal(first, second); }
+
+    virtual bool metavoxelRootsEqual(const MetavoxelNode& firstRoot, const MetavoxelNode& secondRoot,
+        const glm::vec3& minimum, float size, const MetavoxelLOD& lod);
+
     /// Merges the value of a parent and its children.
     /// \param postRead whether or not the merge is happening after a read
     /// \return whether or not the children and parent values are all equal
@@ -406,6 +412,8 @@ public:
     virtual void read(Bitstream& in, void*& value, bool isLeaf) const;
     virtual void write(Bitstream& out, void* value, bool isLeaf) const;
 
+    virtual bool deepEqual(void* first, void* second) const;
+
     virtual bool merge(void*& parent, void* children[], bool postRead = false) const;
     
     virtual void* createFromVariant(const QVariant& value) const;
@@ -434,6 +442,8 @@ public:
     
     virtual MetavoxelNode* createMetavoxelNode(const AttributeValue& value, const MetavoxelNode* original) const;
     
+    virtual bool deepEqual(void* first, void* second) const;
+    
     virtual bool merge(void*& parent, void* children[], bool postRead = false) const;
 
     virtual AttributeValue inherit(const AttributeValue& parentValue) const;
@@ -462,6 +472,9 @@ public:
     
     virtual void readMetavoxelSubdivision(MetavoxelData& data, MetavoxelStreamState& state);
     virtual void writeMetavoxelSubdivision(const MetavoxelNode& root, MetavoxelStreamState& state);
+    
+    virtual bool metavoxelRootsEqual(const MetavoxelNode& firstRoot, const MetavoxelNode& secondRoot,
+        const glm::vec3& minimum, float size, const MetavoxelLOD& lod);
 };
 
 #endif // hifi_AttributeRegistry_h
diff --git a/libraries/metavoxels/src/Bitstream.cpp b/libraries/metavoxels/src/Bitstream.cpp
index 44342abe33..d18903f923 100644
--- a/libraries/metavoxels/src/Bitstream.cpp
+++ b/libraries/metavoxels/src/Bitstream.cpp
@@ -110,6 +110,10 @@ const TypeStreamer* Bitstream::getTypeStreamer(int type) {
     return getTypeStreamers().value(type);
 }
 
+const ObjectStreamer* Bitstream::getObjectStreamer(const QMetaObject* metaObject) {
+    return getObjectStreamers().value(metaObject);
+}
+
 const QMetaObject* Bitstream::getMetaObject(const QByteArray& className) {
     return getMetaObjects().value(className);
 }
@@ -1410,8 +1414,10 @@ Bitstream& Bitstream::operator<(const SharedObjectPointer& object) {
     *this << object->getOriginID();
     QPointer<SharedObject> reference = _sharedObjectReferences.value(object->getOriginID());
     if (reference) {
+        *this << true;
         writeRawDelta((const QObject*)object.data(), (const QObject*)reference.data());
     } else {
+        *this << false;
         *this << (QObject*)object.data();
     }
     return *this;
@@ -1426,19 +1432,27 @@ Bitstream& Bitstream::operator>(SharedObjectPointer& object) {
     }
     int originID;
     *this >> originID;
+    bool delta;
+    *this >> delta;
     QPointer<SharedObject> reference = _sharedObjectReferences.value(originID);
     QPointer<SharedObject>& pointer = _weakSharedObjectHash[id];
     if (pointer) {
         ObjectStreamerPointer objectStreamer;
         _objectStreamerStreamer >> objectStreamer;
-        if (reference) {
+        if (delta) {
+            if (!reference) {
+                qWarning() << "Delta without reference" << id << originID;
+            }
             objectStreamer->readRawDelta(*this, reference.data(), pointer.data());
         } else {
             objectStreamer->read(*this, pointer.data());
         }
     } else {
         QObject* rawObject; 
-        if (reference) {
+        if (delta) {
+            if (!reference) {
+                qWarning() << "Delta without reference" << id << originID;
+            }
             readRawDelta(rawObject, (const QObject*)reference.data());
         } else {
             *this >> rawObject;
@@ -2316,6 +2330,15 @@ QObject* MappedObjectStreamer::putJSONData(JSONReader& reader, const QJsonObject
     return object;
 }
 
+bool MappedObjectStreamer::equal(const QObject* first, const QObject* second) const {
+    foreach (const StreamerPropertyPair& property, _properties) {
+        if (!property.first->equal(property.second.read(first), property.second.read(second))) {
+            return false;
+        }
+    }
+    return true;
+}
+
 void MappedObjectStreamer::write(Bitstream& out, const QObject* object) const {
     foreach (const StreamerPropertyPair& property, _properties) {
         property.first->write(out, property.second.read(object));
@@ -2433,6 +2456,17 @@ QObject* GenericObjectStreamer::putJSONData(JSONReader& reader, const QJsonObjec
     return object;
 }
 
+bool GenericObjectStreamer::equal(const QObject* first, const QObject* second) const {
+    const QVariantList& firstValues = static_cast<const GenericSharedObject*>(first)->getValues();
+    const QVariantList& secondValues = static_cast<const GenericSharedObject*>(second)->getValues();
+    for (int i = 0; i < _properties.size(); i++) {
+        if (!_properties.at(i).first->equal(firstValues.at(i), secondValues.at(i))) {
+            return false;
+        }
+    }
+    return true;
+}
+
 void GenericObjectStreamer::write(Bitstream& out, const QObject* object) const {
     const QVariantList& values = static_cast<const GenericSharedObject*>(object)->getValues();
     for (int i = 0; i < _properties.size(); i++) {
diff --git a/libraries/metavoxels/src/Bitstream.h b/libraries/metavoxels/src/Bitstream.h
index 0d9e516640..e32f93dbe2 100644
--- a/libraries/metavoxels/src/Bitstream.h
+++ b/libraries/metavoxels/src/Bitstream.h
@@ -278,6 +278,9 @@ public:
     /// Returns the streamer registered for the supplied type, if any.
     static const TypeStreamer* getTypeStreamer(int type);
 
+    /// Returns the streamer registered for the supplied object, if any.
+    static const ObjectStreamer* getObjectStreamer(const QMetaObject* metaObject);
+
     /// Returns the meta-object registered under the supplied class name, if any.
     static const QMetaObject* getMetaObject(const QByteArray& className);
 
@@ -1022,6 +1025,7 @@ public:
     virtual QJsonObject getJSONData(JSONWriter& writer, const QObject* object) const = 0;
     virtual QObject* putJSONData(JSONReader& reader, const QJsonObject& jsonObject) const = 0;
     
+    virtual bool equal(const QObject* first, const QObject* second) const = 0;
     virtual void write(Bitstream& out, const QObject* object) const = 0;
     virtual void writeRawDelta(Bitstream& out, const QObject* object, const QObject* reference) const = 0;
     virtual QObject* read(Bitstream& in, QObject* object = NULL) const = 0;
@@ -1047,6 +1051,7 @@ public:
     virtual QJsonObject getJSONMetadata(JSONWriter& writer) const;
     virtual QJsonObject getJSONData(JSONWriter& writer, const QObject* object) const;
     virtual QObject* putJSONData(JSONReader& reader, const QJsonObject& jsonObject) const;
+    virtual bool equal(const QObject* first, const QObject* second) const;
     virtual void write(Bitstream& out, const QObject* object) const;
     virtual void writeRawDelta(Bitstream& out, const QObject* object, const QObject* reference) const;
     virtual QObject* read(Bitstream& in, QObject* object = NULL) const;
@@ -1070,6 +1075,7 @@ public:
     virtual QJsonObject getJSONMetadata(JSONWriter& writer) const;
     virtual QJsonObject getJSONData(JSONWriter& writer, const QObject* object) const;
     virtual QObject* putJSONData(JSONReader& reader, const QJsonObject& jsonObject) const;
+    virtual bool equal(const QObject* first, const QObject* second) const;
     virtual void write(Bitstream& out, const QObject* object) const;
     virtual void writeRawDelta(Bitstream& out, const QObject* object, const QObject* reference) const;
     virtual QObject* read(Bitstream& in, QObject* object = NULL) const;
@@ -1104,7 +1110,7 @@ private:
 Q_DECLARE_METATYPE(const QMetaObject*)
 
 /// Macro for registering streamable meta-objects.  Typically, one would use this macro at the top level of the source file
-/// associated with the class.
+/// associated with the class.  The class should have a no-argument constructor flagged with Q_INVOKABLE.
 #define REGISTER_META_OBJECT(x) static int x##Registration = Bitstream::registerMetaObject(#x, &x::staticMetaObject);
 
 /// Contains a value along with a pointer to its streamer.  This is stored in QVariants when using fallback generics and
@@ -1563,8 +1569,8 @@ public:
     Bitstream::registerTypeStreamer(qMetaTypeId<X>(), new CollectionTypeStreamer<X>());
 
 /// Declares the metatype and the streaming operators.  Typically, one would use this immediately after the definition of a
-/// type flagged as STREAMABLE in its header file.  The last lines ensure that the generated file will be included in the link
-/// phase.
+/// type flagged as STREAMABLE in its header file.  The type should have a no-argument constructor.  The last lines of this
+/// macro ensure that the generated file will be included in the link phase.
 #ifdef _WIN32
 #define DECLARE_STREAMABLE_METATYPE(X) Q_DECLARE_METATYPE(X) \
     Bitstream& operator<<(Bitstream& out, const X& obj); \
diff --git a/libraries/metavoxels/src/DatagramSequencer.cpp b/libraries/metavoxels/src/DatagramSequencer.cpp
index f1f60e4d87..eb02497321 100644
--- a/libraries/metavoxels/src/DatagramSequencer.cpp
+++ b/libraries/metavoxels/src/DatagramSequencer.cpp
@@ -23,6 +23,9 @@ const int MAX_DATAGRAM_SIZE = MAX_PACKET_SIZE;
 
 const int DEFAULT_MAX_PACKET_SIZE = 3000;
 
+// the default slow-start threshold, which will be lowered quickly when we first encounter packet loss
+const float DEFAULT_SLOW_START_THRESHOLD = 1000.0f;
+
 DatagramSequencer::DatagramSequencer(const QByteArray& datagramHeader, QObject* parent) :
     QObject(parent),
     _outgoingPacketStream(&_outgoingPacketData, QIODevice::WriteOnly),
@@ -37,7 +40,12 @@ DatagramSequencer::DatagramSequencer(const QByteArray& datagramHeader, QObject*
     _incomingPacketStream(&_incomingPacketData, QIODevice::ReadOnly),
     _inputStream(_incomingPacketStream),
     _receivedHighPriorityMessages(0),
-    _maxPacketSize(DEFAULT_MAX_PACKET_SIZE) {
+    _maxPacketSize(DEFAULT_MAX_PACKET_SIZE),
+    _packetsPerGroup(1.0f),
+    _packetsToWrite(0.0f),
+    _slowStartThreshold(DEFAULT_SLOW_START_THRESHOLD),
+    _packetRateIncreasePacketNumber(0),
+    _packetRateDecreasePacketNumber(0) {
 
     _outgoingPacketStream.setByteOrder(QDataStream::LittleEndian);
     _incomingDatagramStream.setByteOrder(QDataStream::LittleEndian);
@@ -71,6 +79,33 @@ ReliableChannel* DatagramSequencer::getReliableInputChannel(int index) {
     return channel;
 }
 
+int DatagramSequencer::startPacketGroup(int desiredPackets) {
+    // figure out how much data we have enqueued and increase the number of packets desired
+    int totalAvailable = 0;
+    foreach (ReliableChannel* channel, _reliableOutputChannels) {
+        totalAvailable += channel->getBytesAvailable();
+    }
+    desiredPackets += (totalAvailable / _maxPacketSize);
+
+    // increment our packet counter and subtract/return the integer portion
+    _packetsToWrite += _packetsPerGroup;
+    int wholePackets = (int)_packetsToWrite;
+    _packetsToWrite -= wholePackets;
+    wholePackets = qMin(wholePackets, desiredPackets);
+    
+    // if we don't want to send any more, push out the rate increase number past the group
+    if (desiredPackets <= _packetsPerGroup) {
+        _packetRateIncreasePacketNumber = _outgoingPacketNumber + wholePackets + 1;
+    }
+    
+    // likewise, if we're only sending one packet, don't let its loss cause rate decrease
+    if (wholePackets == 1) {
+        _packetRateDecreasePacketNumber = _outgoingPacketNumber + 2;
+    }
+    
+    return wholePackets;
+}
+
 Bitstream& DatagramSequencer::startPacket() {
     // start with the list of acknowledgements
     _outgoingPacketStream << (quint32)_receiveRecords.size();
@@ -105,6 +140,12 @@ void DatagramSequencer::endPacket() {
     _outgoingPacketStream.device()->seek(0);
 }
 
+void DatagramSequencer::cancelPacket() {
+    _outputStream.reset();
+    _outputStream.getAndResetWriteMappings();
+    _outgoingPacketStream.device()->seek(0);
+}
+
 /// Simple RAII-style object to keep a device open when in scope.
 class QIODeviceOpener {
 public:
@@ -172,7 +213,10 @@ void DatagramSequencer::receivedDatagram(const QByteArray& datagram) {
         if (index < 0 || index >= _sendRecords.size()) {
             continue;
         }
-        QList<SendRecord>::iterator it = _sendRecords.begin() + index;
+        QList<SendRecord>::iterator it = _sendRecords.begin();
+        for (int i = 0; i < index; i++) {
+            sendRecordLost(*it++);
+        }
         sendRecordAcknowledged(*it);
         emit sendAcknowledged(index);
         _sendRecords.erase(_sendRecords.begin(), it + 1);
@@ -253,6 +297,28 @@ void DatagramSequencer::sendRecordAcknowledged(const SendRecord& record) {
     foreach (const ChannelSpan& span, record.spans) {
         getReliableOutputChannel(span.channel)->spanAcknowledged(span);
     }
+    
+    // increase the packet rate with every ack until we pass the slow start threshold; then, every round trip
+    if (record.packetNumber >= _packetRateIncreasePacketNumber) {
+        if (_packetsPerGroup >= _slowStartThreshold) {
+            _packetRateIncreasePacketNumber = _outgoingPacketNumber + 1;
+        }
+        _packetsPerGroup += 1.0f;
+    }
+}
+
+void DatagramSequencer::sendRecordLost(const SendRecord& record) {
+    // notify the channels of their lost spans
+    foreach (const ChannelSpan& span, record.spans) {
+        getReliableOutputChannel(span.channel)->spanLost(record.packetNumber, _outgoingPacketNumber + 1);
+    }
+    
+    // halve the rate and remember as threshold
+    if (record.packetNumber >= _packetRateDecreasePacketNumber) {
+        _packetsPerGroup = qMax(_packetsPerGroup * 0.5f, 1.0f);
+        _slowStartThreshold = _packetsPerGroup;
+        _packetRateDecreasePacketNumber = _outgoingPacketNumber + 1;
+    }
 }
 
 void DatagramSequencer::appendReliableData(int bytes, QVector<ChannelSpan>& spans) {
@@ -520,7 +586,9 @@ int SpanList::set(int offset, int length) {
 
     // look for an intersection within the list
     int position = 0;
-    for (QList<Span>::iterator it = _spans.begin(); it != _spans.end(); it++) {
+    for (int i = 0; i < _spans.size(); i++) {
+        QList<Span>::iterator it = _spans.begin() + i;
+    
         // if we intersect the unset portion, contract it
         position += it->unset;
         if (offset <= position) {
@@ -530,16 +598,20 @@ int SpanList::set(int offset, int length) {
             // if we continue into the set portion, expand it and consume following spans
             int extra = offset + length - position;
             if (extra >= 0) {
-                int amount = setSpans(it + 1, extra);
-                it->set += amount;
-                _totalSet += amount;
-            
+                extra -= it->set;
+                it->set += remove;
+                _totalSet += remove;
+                if (extra > 0) {
+                    int amount = setSpans(it + 1, extra);
+                    _spans[i].set += amount;
+                    _totalSet += amount;
+                }
             // otherwise, insert a new span
             } else {        
-                Span span = { it->unset, length + extra };
-                _spans.insert(it, span);
+                Span span = { it->unset, length };
                 it->unset = -extra;
-                _totalSet += span.set;
+                _spans.insert(it, span);
+                _totalSet += length;
             }
             return 0;
         }
@@ -548,9 +620,11 @@ int SpanList::set(int offset, int length) {
         position += it->set;
         if (offset <= position) {
             int extra = offset + length - position;
-            int amount = setSpans(it + 1, extra);
-            it->set += amount;
-            _totalSet += amount;
+            if (extra > 0) {
+                int amount = setSpans(it + 1, extra);
+                _spans[i].set += amount;
+                _totalSet += amount;
+            }
             return 0;
         }
     }
@@ -619,6 +693,7 @@ ReliableChannel::ReliableChannel(DatagramSequencer* sequencer, int index, bool o
     _priority(1.0f),
     _offset(0),
     _writePosition(0),
+    _writePositionResetPacketNumber(0),
     _messagesEnabled(true) {
     
     _buffer.open(output ? QIODevice::WriteOnly : QIODevice::ReadOnly);
@@ -629,67 +704,76 @@ ReliableChannel::ReliableChannel(DatagramSequencer* sequencer, int index, bool o
 }
 
 void ReliableChannel::writeData(QDataStream& out, int bytes, QVector<DatagramSequencer::ChannelSpan>& spans) {
-    // find out how many spans we want to write
-    int spanCount = 0;
-    int remainingBytes = bytes;
-    bool first = true;
-    while (remainingBytes > 0) {
-        int position = 0;
-        foreach (const SpanList::Span& span, _acknowledged.getSpans()) {
-            if (remainingBytes <= 0) {
-                break;
-            }
-            spanCount++;
-            remainingBytes -= getBytesToWrite(first, qMin(remainingBytes, span.unset));
-            position += (span.unset + span.set);
-        }
-        int leftover = _buffer.pos() - position;
-        if (remainingBytes > 0 && leftover > 0) {
-            spanCount++;
-            remainingBytes -= getBytesToWrite(first, qMin(remainingBytes, leftover));
-        }
+    if (bytes == 0) {
+        out << (quint32)0;
+        return;
     }
-    
-    // write the count and the spans
-    out << (quint32)spanCount;
-    remainingBytes = bytes;
-    first = true;
-    while (remainingBytes > 0) {
+    _writePosition %= _buffer.pos();
+    while (bytes > 0) {
         int position = 0;
-        foreach (const SpanList::Span& span, _acknowledged.getSpans()) {
-            if (remainingBytes <= 0) {
-                break;
+        for (int i = 0; i < _acknowledged.getSpans().size(); i++) {
+            const SpanList::Span& span = _acknowledged.getSpans().at(i);
+            position += span.unset;
+            if (_writePosition < position) {
+                int start = qMax(position - span.unset, _writePosition);
+                int length = qMin(bytes, position - start);
+                writeSpan(out, start, length, spans);
+                writeFullSpans(out, bytes - length, i + 1, position + span.set, spans);
+                out << (quint32)0;
+                return;
             }
-            remainingBytes -= writeSpan(out, first, position, qMin(remainingBytes, span.unset), spans);
-            position += (span.unset + span.set);
+            position += span.set;
         }
         int leftover = _buffer.pos() - position;
-        if (remainingBytes > 0 && leftover > 0) {
-            remainingBytes -= writeSpan(out, first, position, qMin(remainingBytes, leftover), spans);
+        position = _buffer.pos();
+        
+        if (_writePosition < position && leftover > 0) {
+            int start = qMax(position - leftover, _writePosition);
+            int length = qMin(bytes, position - start);
+            writeSpan(out, start, length, spans);
+            writeFullSpans(out, bytes - length, 0, 0, spans);
+            out << (quint32)0;
+            return;
+        }
+        _writePosition = 0;
+    }
+}
+
+void ReliableChannel::writeFullSpans(QDataStream& out, int bytes, int startingIndex, int position,
+        QVector<DatagramSequencer::ChannelSpan>& spans) {
+    int expandedSize = _acknowledged.getSpans().size() + 1;
+    for (int i = 0; i < expandedSize; i++) {
+        if (bytes == 0) {
+            return;
+        }
+        int index = (startingIndex + i) % expandedSize;
+        if (index == _acknowledged.getSpans().size()) {
+            int leftover = _buffer.pos() - position;
+            if (leftover > 0) {
+                int length = qMin(leftover, bytes);
+                writeSpan(out, position, length, spans);
+                bytes -= length;
+            }
+            position = 0;
+            
+        } else {
+            const SpanList::Span& span = _acknowledged.getSpans().at(index);
+            int length = qMin(span.unset, bytes);
+            writeSpan(out, position, length, spans);
+            bytes -= length;
+            position += (span.unset + span.set);
         }
     }
 }
 
-int ReliableChannel::getBytesToWrite(bool& first, int length) const {
-    if (first) {
-        first = false;
-        return length - (_writePosition % length);
-    }
-    return length;
-}
-
-int ReliableChannel::writeSpan(QDataStream& out, bool& first, int position, int length, QVector<DatagramSequencer::ChannelSpan>& spans) {
-    if (first) {
-        first = false;
-        position = _writePosition % length;
-        length -= position;
-        _writePosition += length;
-    }
+int ReliableChannel::writeSpan(QDataStream& out, int position, int length, QVector<DatagramSequencer::ChannelSpan>& spans) {
     DatagramSequencer::ChannelSpan span = { _index, _offset + position, length };
     spans.append(span);
-    out << (quint32)span.offset;
     out << (quint32)length;
+    out << (quint32)span.offset;
     _buffer.writeToStream(position, length, out);
+    _writePosition = position + length;
+    
     return length;
 }
 
@@ -700,17 +784,28 @@ void ReliableChannel::spanAcknowledged(const DatagramSequencer::ChannelSpan& spa
         _buffer.seek(_buffer.size());
         
         _offset += advancement;
-        _writePosition = qMax(_writePosition - advancement, 0);
-    } 
+        _writePosition = qMax(_writePosition - advancement, 0);   
+    }
+}
+
+void ReliableChannel::spanLost(int packetNumber, int nextOutgoingPacketNumber) {
+    // reset the write position up to once each round trip time
+    if (packetNumber >= _writePositionResetPacketNumber) {
+        _writePosition = 0;
+        _writePositionResetPacketNumber = nextOutgoingPacketNumber;
+    }
 }
 
 void ReliableChannel::readData(QDataStream& in) {
-    quint32 segments;
-    in >> segments;
     bool readSome = false;
-    for (quint32 i = 0; i < segments; i++) {
-        quint32 offset, size;
-        in >> offset >> size;
+    forever {
+        quint32 size;
+        in >> size;
+        if (size == 0) {
+            break;
+        }
+        quint32 offset;
+        in >> offset;
         
         int position = offset - _offset;
         int end = position + size;
diff --git a/libraries/metavoxels/src/DatagramSequencer.h b/libraries/metavoxels/src/DatagramSequencer.h
index 5ac88556f0..aa8b6907ff 100644
--- a/libraries/metavoxels/src/DatagramSequencer.h
+++ b/libraries/metavoxels/src/DatagramSequencer.h
@@ -99,6 +99,11 @@ public:
     /// Returns the intput channel at the specified index, creating it if necessary.
     ReliableChannel* getReliableInputChannel(int index = 0);
     
+    /// Starts a packet group.
+    /// \param desiredPackets the number of packets we'd like to write in the group
+    /// \return the number of packets to write in the group
+    int startPacketGroup(int desiredPackets = 1);
+    
     /// Starts a new packet for transmission.
     /// \return a reference to the Bitstream to use for writing to the packet
     Bitstream& startPacket();
@@ -106,6 +111,9 @@ public:
     /// Sends the packet currently being written. 
     void endPacket();
     
+    /// Cancels the packet currently being written.
+    void cancelPacket();
+    
     /// Processes a datagram received from the other party, emitting readyToRead when the entire packet
     /// has been successfully assembled.
     Q_INVOKABLE void receivedDatagram(const QByteArray& datagram);
@@ -165,6 +173,9 @@ private:
     /// Notes that the described send was acknowledged by the other party.
     void sendRecordAcknowledged(const SendRecord& record);
     
+    /// Notes that the described send was lost in transit.
+    void sendRecordLost(const SendRecord& record);
+    
     /// Appends some reliable data to the outgoing packet.
     void appendReliableData(int bytes, QVector<ChannelSpan>& spans);
     
@@ -200,6 +211,12 @@ private:
     
     int _maxPacketSize;
     
+    float _packetsPerGroup;
+    float _packetsToWrite;
+    float _slowStartThreshold;
+    int _packetRateIncreasePacketNumber;
+    int _packetRateDecreasePacketNumber;
+    
     QHash<int, ReliableChannel*> _reliableOutputChannels;
     QHash<int, ReliableChannel*> _reliableInputChannels;
 };
@@ -343,10 +360,12 @@ private:
     ReliableChannel(DatagramSequencer* sequencer, int index, bool output);
     
     void writeData(QDataStream& out, int bytes, QVector<DatagramSequencer::ChannelSpan>& spans);
-    int getBytesToWrite(bool& first, int length) const;
-    int writeSpan(QDataStream& out, bool& first, int position, int length, QVector<DatagramSequencer::ChannelSpan>& spans);
+    void writeFullSpans(QDataStream& out, int bytes, int startingIndex, int position,
+        QVector<DatagramSequencer::ChannelSpan>& spans);
+    int writeSpan(QDataStream& out, int position, int length, QVector<DatagramSequencer::ChannelSpan>& spans);
     
     void spanAcknowledged(const DatagramSequencer::ChannelSpan& span);
+    void spanLost(int packetNumber, int nextOutgoingPacketNumber);
     
     void readData(QDataStream& in);
     
@@ -359,6 +378,7 @@ private:
     
     int _offset;
     int _writePosition;
+    int _writePositionResetPacketNumber;
     SpanList _acknowledged;
     bool _messagesEnabled;
 };
diff --git a/libraries/metavoxels/src/Endpoint.cpp b/libraries/metavoxels/src/Endpoint.cpp
new file mode 100644
index 0000000000..c656054504
--- /dev/null
+++ b/libraries/metavoxels/src/Endpoint.cpp
@@ -0,0 +1,109 @@
+//
+//  Endpoint.cpp
+//  libraries/metavoxels/src
+//
+//  Created by Andrzej Kapolka on 6/26/14.
+//  Copyright 2014 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
+//
+
+#include <PacketHeaders.h>
+
+#include "Endpoint.h"
+
+Endpoint::Endpoint(const SharedNodePointer& node, PacketRecord* baselineSendRecord, PacketRecord* baselineReceiveRecord) :
+    _node(node),
+    _sequencer(byteArrayWithPopulatedHeader(PacketTypeMetavoxelData)) {
+    
+    connect(&_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendDatagram(const QByteArray&)));
+    connect(&_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readMessage(Bitstream&)));
+    connect(&_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(clearSendRecordsBefore(int)));
+    connect(&_sequencer, SIGNAL(receiveAcknowledged(int)), SLOT(clearReceiveRecordsBefore(int)));
+    
+    // insert the baseline send and receive records
+    _sendRecords.append(baselineSendRecord);
+    _receiveRecords.append(baselineReceiveRecord);
+}
+
+Endpoint::~Endpoint() {
+    foreach (PacketRecord* record, _sendRecords) {
+        delete record;
+    }
+    foreach (PacketRecord* record, _receiveRecords) {
+        delete record;
+    }
+}
+
+void Endpoint::update() {
+    Bitstream& out = _sequencer.startPacket();
+    writeUpdateMessage(out);
+    _sequencer.endPacket();
+
+    // record the send
+    _sendRecords.append(maybeCreateSendRecord());
+}
+
+int Endpoint::parseData(const QByteArray& packet) {
+    // process through sequencer
+    _sequencer.receivedDatagram(packet);
+    return packet.size();
+}
+
+void Endpoint::sendDatagram(const QByteArray& data) {
+    NodeList::getInstance()->writeDatagram(data, _node);
+}
+
+void Endpoint::readMessage(Bitstream& in) {
+    QVariant message;
+    in >> message;
+    handleMessage(message, in);
+    
+    // record the receipt
+    _receiveRecords.append(maybeCreateReceiveRecord());
+}
+
+void Endpoint::clearSendRecordsBefore(int index) {
+    QList<PacketRecord*>::iterator end = _sendRecords.begin() + index + 1;
+    for (QList<PacketRecord*>::const_iterator it = _sendRecords.begin(); it != end; it++) {
+        delete *it;
+    }
+    _sendRecords.erase(_sendRecords.begin(), end);
+}
+
+void Endpoint::clearReceiveRecordsBefore(int index) {
+    QList<PacketRecord*>::iterator end = _receiveRecords.begin() + index + 1;
+    for (QList<PacketRecord*>::const_iterator it = _receiveRecords.begin(); it != end; it++) {
+        delete *it;
+    }
+    _receiveRecords.erase(_receiveRecords.begin(), end);
+}
+
+void Endpoint::writeUpdateMessage(Bitstream& out) {
+    out << QVariant();
+}
+
+void Endpoint::handleMessage(const QVariant& message, Bitstream& in) {
+    if (message.userType() == QMetaType::QVariantList) {
+        foreach (const QVariant& element, message.toList()) {
+            handleMessage(element, in);
+        }
+    }
+}
+
+PacketRecord* Endpoint::maybeCreateSendRecord() const {
+    return NULL;
+}
+
+PacketRecord* Endpoint::maybeCreateReceiveRecord() const {
+    return NULL;
+}
+
+PacketRecord::PacketRecord(const MetavoxelLOD& lod, const MetavoxelData& data) :
+    _lod(lod),
+    _data(data) {
+}
+
+PacketRecord::~PacketRecord() {
+}
diff --git a/libraries/metavoxels/src/Endpoint.h b/libraries/metavoxels/src/Endpoint.h
new file mode 100644
index 0000000000..d253a69ded
--- /dev/null
+++ b/libraries/metavoxels/src/Endpoint.h
@@ -0,0 +1,78 @@
+//
+//  Endpoint.h
+//  libraries/metavoxels/src
+//
+//  Created by Andrzej Kapolka on 6/26/14.
+//  Copyright 2014 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
+//
+
+#ifndef hifi_Endpoint_h
+#define hifi_Endpoint_h
+
+#include <NodeList.h>
+
+#include "DatagramSequencer.h"
+#include "MetavoxelData.h"
+
+class PacketRecord;
+
+/// Base class for communication endpoints: clients and server sessions.
+class Endpoint : public NodeData {
+    Q_OBJECT
+
+public:
+    
+    Endpoint(const SharedNodePointer& node, PacketRecord* baselineSendRecord = NULL,
+        PacketRecord* baselineReceiveRecord = NULL);
+    virtual ~Endpoint();
+    
+    virtual void update();
+    
+    virtual int parseData(const QByteArray& packet);
+
+protected slots:
+
+    virtual void sendDatagram(const QByteArray& data);
+    virtual void readMessage(Bitstream& in);
+    
+    void clearSendRecordsBefore(int index);
+    void clearReceiveRecordsBefore(int index);
+
+protected:
+
+    virtual void writeUpdateMessage(Bitstream& out);
+    virtual void handleMessage(const QVariant& message, Bitstream& in); 
+    
+    virtual PacketRecord* maybeCreateSendRecord() const;
+    virtual PacketRecord* maybeCreateReceiveRecord() const;
+    
+    PacketRecord* getLastAcknowledgedSendRecord() const { return _sendRecords.first(); }
+    PacketRecord* getLastAcknowledgedReceiveRecord() const { return _receiveRecords.first(); }
+    
+    SharedNodePointer _node;
+    DatagramSequencer _sequencer;
+    
+    QList<PacketRecord*> _sendRecords;
+    QList<PacketRecord*> _receiveRecords;
+};
+
+/// Base class for packet records.
+class PacketRecord {
+public:
+
+    PacketRecord(const MetavoxelLOD& lod = MetavoxelLOD(), const MetavoxelData& data = MetavoxelData());
+    virtual ~PacketRecord();
+    
+    const MetavoxelLOD& getLOD() const { return _lod; }
+    const MetavoxelData& getData() const { return _data; }
+    
+private:
+    
+    MetavoxelLOD _lod;
+    MetavoxelData _data;
+};
+
+#endif // hifi_Endpoint_h
diff --git a/libraries/metavoxels/src/MetavoxelClientManager.cpp b/libraries/metavoxels/src/MetavoxelClientManager.cpp
new file mode 100644
index 0000000000..008a477187
--- /dev/null
+++ b/libraries/metavoxels/src/MetavoxelClientManager.cpp
@@ -0,0 +1,142 @@
+//
+//  MetavoxelClientManager.cpp
+//  libraries/metavoxels/src
+//
+//  Created by Andrzej Kapolka on 6/26/14.
+//  Copyright 2014 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
+//
+
+#include "MetavoxelClientManager.h"
+#include "MetavoxelMessages.h"
+
+void MetavoxelClientManager::init() {
+    connect(NodeList::getInstance(), SIGNAL(nodeAdded(SharedNodePointer)), SLOT(maybeAttachClient(const SharedNodePointer&)));
+}
+
+void MetavoxelClientManager::update() {
+    foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
+        if (node->getType() == NodeType::MetavoxelServer) {
+            QMutexLocker locker(&node->getMutex());
+            MetavoxelClient* client = static_cast<MetavoxelClient*>(node->getLinkedData());
+            if (client) {
+                updateClient(client);
+            }
+        }
+    }
+}
+
+SharedObjectPointer MetavoxelClientManager::findFirstRaySpannerIntersection(const glm::vec3& origin,
+        const glm::vec3& direction, const AttributePointer& attribute, float& distance) {
+    SharedObjectPointer closestSpanner;
+    float closestDistance = FLT_MAX;
+    foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
+        if (node->getType() == NodeType::MetavoxelServer) {
+            QMutexLocker locker(&node->getMutex());
+            MetavoxelClient* client = static_cast<MetavoxelClient*>(node->getLinkedData());
+            if (client) {
+                float clientDistance;
+                SharedObjectPointer clientSpanner = client->getData().findFirstRaySpannerIntersection(
+                    origin, direction, attribute, clientDistance);
+                if (clientSpanner && clientDistance < closestDistance) {
+                    closestSpanner = clientSpanner;
+                    closestDistance = clientDistance;
+                }
+            }
+        }
+    }
+    if (closestSpanner) {
+        distance = closestDistance;
+    }
+    return closestSpanner;
+}
+
+void MetavoxelClientManager::applyEdit(const MetavoxelEditMessage& edit, bool reliable) {
+    foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
+        if (node->getType() == NodeType::MetavoxelServer) {
+            QMutexLocker locker(&node->getMutex());
+            MetavoxelClient* client = static_cast<MetavoxelClient*>(node->getLinkedData());
+            if (client) {
+                client->applyEdit(edit, reliable);
+            }
+        }
+    }
+}
+
+MetavoxelLOD MetavoxelClientManager::getLOD() const {
+    return MetavoxelLOD();
+}
+
+void MetavoxelClientManager::maybeAttachClient(const SharedNodePointer& node) {
+    if (node->getType() == NodeType::MetavoxelServer) {
+        QMutexLocker locker(&node->getMutex());
+        node->setLinkedData(createClient(node));
+    }
+}
+
+MetavoxelClient* MetavoxelClientManager::createClient(const SharedNodePointer& node) {
+    return new MetavoxelClient(node, this);
+}
+
+void MetavoxelClientManager::updateClient(MetavoxelClient* client) {
+    client->update();
+}
+
+MetavoxelClient::MetavoxelClient(const SharedNodePointer& node, MetavoxelClientManager* manager) :
+    Endpoint(node, new PacketRecord(), new PacketRecord()),
+    _manager(manager) {
+}
+
+void MetavoxelClient::guide(MetavoxelVisitor& visitor) {
+    visitor.setLOD(_manager->getLOD());
+    _data.guide(visitor);
+}
+
+void MetavoxelClient::applyEdit(const MetavoxelEditMessage& edit, bool reliable) {
+    if (reliable) {
+        _sequencer.getReliableOutputChannel()->sendMessage(QVariant::fromValue(edit));
+    
+    } else {
+        // apply immediately to local tree
+        edit.apply(_data, _sequencer.getWeakSharedObjectHash());
+
+        // start sending it out
+        _sequencer.sendHighPriorityMessage(QVariant::fromValue(edit));
+    }
+}
+
+void MetavoxelClient::writeUpdateMessage(Bitstream& out) {
+    ClientStateMessage state = { _manager->getLOD() };
+    out << QVariant::fromValue(state);
+}
+
+void MetavoxelClient::readMessage(Bitstream& in) {
+    Endpoint::readMessage(in);
+    
+    // reapply local edits
+    foreach (const DatagramSequencer::HighPriorityMessage& message, _sequencer.getHighPriorityMessages()) {
+        if (message.data.userType() == MetavoxelEditMessage::Type) {
+            message.data.value<MetavoxelEditMessage>().apply(_data, _sequencer.getWeakSharedObjectHash());
+        }
+    }
+}
+
+void MetavoxelClient::handleMessage(const QVariant& message, Bitstream& in) {
+    if (message.userType() == MetavoxelDeltaMessage::Type) {
+        PacketRecord* receiveRecord = getLastAcknowledgedReceiveRecord();
+        _data.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, getLastAcknowledgedSendRecord()->getLOD());
+    
+    } else {
+        Endpoint::handleMessage(message, in);
+    }
+}
+
+PacketRecord* MetavoxelClient::maybeCreateSendRecord() const {
+    return new PacketRecord(_manager->getLOD());
+}
+
+PacketRecord* MetavoxelClient::maybeCreateReceiveRecord() const {
+    return new PacketRecord(getLastAcknowledgedSendRecord()->getLOD(), _data);
+}
diff --git a/libraries/metavoxels/src/MetavoxelClientManager.h b/libraries/metavoxels/src/MetavoxelClientManager.h
new file mode 100644
index 0000000000..dd11e871ec
--- /dev/null
+++ b/libraries/metavoxels/src/MetavoxelClientManager.h
@@ -0,0 +1,75 @@
+//
+//  MetavoxelClientManager.h
+//  libraries/metavoxels/src
+//
+//  Created by Andrzej Kapolka on 6/26/14.
+//  Copyright 2014 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
+//
+
+#ifndef hifi_MetavoxelClientManager_h
+#define hifi_MetavoxelClientManager_h
+
+#include "Endpoint.h"
+
+class MetavoxelClient;
+class MetavoxelEditMessage;
+
+/// Manages the set of connected metavoxel clients.
+class MetavoxelClientManager : public QObject {
+    Q_OBJECT
+
+public:
+
+    virtual void init();
+    void update();
+
+    SharedObjectPointer findFirstRaySpannerIntersection(const glm::vec3& origin, const glm::vec3& direction,
+        const AttributePointer& attribute, float& distance);
+        
+    Q_INVOKABLE void applyEdit(const MetavoxelEditMessage& edit, bool reliable = false);
+
+    virtual MetavoxelLOD getLOD() const;
+    
+private slots:
+
+    void maybeAttachClient(const SharedNodePointer& node);
+
+protected:
+    
+    virtual MetavoxelClient* createClient(const SharedNodePointer& node);
+    virtual void updateClient(MetavoxelClient* client);
+};
+
+/// Base class for metavoxel clients.
+class MetavoxelClient : public Endpoint {
+    Q_OBJECT
+
+public:
+    
+    MetavoxelClient(const SharedNodePointer& node, MetavoxelClientManager* manager);
+
+    MetavoxelData& getData() { return _data; }
+
+    void guide(MetavoxelVisitor& visitor);
+    
+    void applyEdit(const MetavoxelEditMessage& edit, bool reliable = false);
+
+protected:
+
+    virtual void writeUpdateMessage(Bitstream& out);
+    virtual void readMessage(Bitstream& in);
+    virtual void handleMessage(const QVariant& message, Bitstream& in);
+
+    virtual PacketRecord* maybeCreateSendRecord() const;
+    virtual PacketRecord* maybeCreateReceiveRecord() const;
+
+private:
+    
+    MetavoxelClientManager* _manager;
+    MetavoxelData _data;
+};
+
+#endif // hifi_MetavoxelClientManager_h
diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp
index 0d52fc5ed6..2d61ede796 100644
--- a/libraries/metavoxels/src/MetavoxelData.cpp
+++ b/libraries/metavoxels/src/MetavoxelData.cpp
@@ -610,6 +610,23 @@ MetavoxelNode* MetavoxelData::createRoot(const AttributePointer& attribute) {
     return root = new MetavoxelNode(attribute);
 }
 
+bool MetavoxelData::deepEquals(const MetavoxelData& other, const MetavoxelLOD& lod) const {
+    if (_size != other._size) {
+        return false;
+    }
+    if (_roots.size() != other._roots.size()) {
+        return false;
+    }
+    glm::vec3 minimum = getMinimum();
+    for (QHash<AttributePointer, MetavoxelNode*>::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) {
+        MetavoxelNode* otherNode = other._roots.value(it.key());
+        if (!(otherNode && it.key()->metavoxelRootsEqual(*it.value(), *otherNode, minimum, _size, lod))) {
+            return false;
+        }
+    }
+    return true;
+}
+
 bool MetavoxelData::operator==(const MetavoxelData& other) const {
     return _size == other._size && _roots == other._roots;
 }
@@ -1006,6 +1023,44 @@ void MetavoxelNode::clearChildren(const AttributePointer& attribute) {
     }
 }
 
+bool MetavoxelNode::deepEquals(const AttributePointer& attribute, const MetavoxelNode& other,
+        const glm::vec3& minimum, float size, const MetavoxelLOD& lod) const {
+    if (!attribute->deepEqual(_attributeValue, other._attributeValue)) {
+        return false;
+    }
+    if (!lod.shouldSubdivide(minimum, size, attribute->getLODThresholdMultiplier())) {
+        return true;
+    }
+    bool leaf = isLeaf(), otherLeaf = other.isLeaf();
+    if (leaf && otherLeaf) {
+        return true;
+    }
+    if (leaf || otherLeaf) {
+        return false;
+    }
+    float nextSize = size * 0.5f;
+    for (int i = 0; i < CHILD_COUNT; i++) {
+        glm::vec3 nextMinimum = getNextMinimum(minimum, nextSize, i);
+        if (!_children[i]->deepEquals(attribute, *(other._children[i]), nextMinimum, nextSize, lod)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+void MetavoxelNode::getSpanners(const AttributePointer& attribute, const glm::vec3& minimum,
+        float size, const MetavoxelLOD& lod, SharedObjectSet& results) const {
+    results.unite(decodeInline<SharedObjectSet>(_attributeValue));
+    if (isLeaf() || !lod.shouldSubdivide(minimum, size, attribute->getLODThresholdMultiplier())) {
+        return;
+    }
+    float nextSize = size * 0.5f;
+    for (int i = 0; i < CHILD_COUNT; i++) {
+        glm::vec3 nextMinimum = getNextMinimum(minimum, nextSize, i);
+        _children[i]->getSpanners(attribute, nextMinimum, nextSize, lod, results);
+    }
+}
+
 int MetavoxelVisitor::encodeOrder(int first, int second, int third, int fourth,
         int fifth, int sixth, int seventh, int eighth) {
     return first | (second << 3) | (third << 6) | (fourth << 9) |
@@ -1034,6 +1089,25 @@ int MetavoxelVisitor::encodeOrder(const glm::vec3& direction) {
         indexDistances.at(6).index, indexDistances.at(7).index);
 }
 
+const int ORDER_ELEMENT_BITS = 3;
+const int ORDER_ELEMENT_MASK = (1 << ORDER_ELEMENT_BITS) - 1;
+
+int MetavoxelVisitor::encodeRandomOrder() {
+    // see http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_.22inside-out.22_algorithm
+    int order = 0;
+    int randomValues = rand();
+    for (int i = 0, iShift = 0; i < MetavoxelNode::CHILD_COUNT; i++, iShift += ORDER_ELEMENT_BITS) {
+        int j = (randomValues >> iShift) % (i + 1);
+        int jShift = j * ORDER_ELEMENT_BITS;
+        if (j != i) {
+            int jValue = (order >> jShift) & ORDER_ELEMENT_MASK;
+            order |= (jValue << iShift);
+        }
+        order = (order & ~(ORDER_ELEMENT_MASK << jShift)) | (i << jShift);
+    }
+    return order;
+}
+
 const int MetavoxelVisitor::DEFAULT_ORDER = encodeOrder(0, 1, 2, 3, 4, 5, 6, 7);
 const int MetavoxelVisitor::STOP_RECURSION = 0;
 const int MetavoxelVisitor::SHORT_CIRCUIT = -1;
@@ -1227,8 +1301,6 @@ bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
             QVector<OwnedAttributeValue>(visitation.outputNodes.size()) } };
     for (int i = 0; i < MetavoxelNode::CHILD_COUNT; i++) {
         // the encoded order tells us the child indices for each iteration
-        const int ORDER_ELEMENT_BITS = 3;
-        const int ORDER_ELEMENT_MASK = (1 << ORDER_ELEMENT_BITS) - 1;
         int index = encodedOrder & ORDER_ELEMENT_MASK;
         encodedOrder >>= ORDER_ELEMENT_BITS;
         for (int j = 0; j < visitation.inputNodes.size(); j++) {
@@ -1269,7 +1341,7 @@ bool DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) {
                 }
             }
             MetavoxelNode* node = visitation.outputNodes.at(j);
-            MetavoxelNode* child = node->getChild(i);
+            MetavoxelNode* child = node->getChild(index);
             if (child) {
                 child->decrementReferenceCount(value.getAttribute());
             } else {
diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h
index 2e6f6c4437..6a7ba33eb5 100644
--- a/libraries/metavoxels/src/MetavoxelData.h
+++ b/libraries/metavoxels/src/MetavoxelData.h
@@ -34,7 +34,8 @@ class NetworkValue;
 class Spanner;
 class SpannerRenderer;
 
-/// Determines whether to subdivide each node when traversing.
+/// Determines whether to subdivide each node when traversing.  Contains the position (presumed to be of the viewer) and a
+/// threshold value, where lower thresholds cause smaller/more distant voxels to be subdivided.
 class MetavoxelLOD {
     STREAMABLE
     
@@ -46,6 +47,7 @@ public:
     
     bool isValid() const { return threshold > 0.0f; }
     
+    /// Checks whether, according to this LOD, we should subdivide the described voxel.
     bool shouldSubdivide(const glm::vec3& minimum, float size, float multiplier = 1.0f) const;
     
     /// Checks whether the node or any of the nodes underneath it have had subdivision enabled as compared to the reference.
@@ -54,7 +56,8 @@ public:
 
 DECLARE_STREAMABLE_METATYPE(MetavoxelLOD)
 
-/// The base metavoxel representation shared between server and client.
+/// The base metavoxel representation shared between server and client.  Contains a size (for all dimensions) and a set of
+/// octrees for different attributes.  
 class MetavoxelData {
 public:
 
@@ -64,30 +67,38 @@ public:
 
     MetavoxelData& operator=(const MetavoxelData& other);
 
+    /// Sets the size in all dimensions.
     void setSize(float size) { _size = size; }
     float getSize() const { return _size; }
 
+    /// Returns the minimum extent of the octrees (which are centered about the origin).
     glm::vec3 getMinimum() const { return glm::vec3(_size, _size, _size) * -0.5f; }
 
+    /// Returns the bounds of the octrees.
     Box getBounds() const;
 
     /// Applies the specified visitor to the contained voxels.
     void guide(MetavoxelVisitor& visitor);
    
+    /// Inserts a spanner into the specified attribute layer.
     void insert(const AttributePointer& attribute, const SharedObjectPointer& object);
     void insert(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object);
     
+    /// Removes a spanner from the specified attribute layer.
     void remove(const AttributePointer& attribute, const SharedObjectPointer& object);
     void remove(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object);
 
+    /// Toggles the existence of a spanner in the specified attribute layer (removes if present, adds if not).
     void toggle(const AttributePointer& attribute, const SharedObjectPointer& object);
     void toggle(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& object);
 
+    /// Replaces a spanner in the specified attribute layer.
     void replace(const AttributePointer& attribute, const SharedObjectPointer& oldObject,
         const SharedObjectPointer& newObject);
     void replace(const AttributePointer& attribute, const Box& bounds, float granularity, const SharedObjectPointer& oldObject,
         const SharedObjectPointer& newObject);
-        
+    
+    /// Clears all data in the specified attribute layer.
     void clear(const AttributePointer& attribute);
 
     /// Convenience function that finds the first spanner intersecting the provided ray.    
@@ -97,7 +108,7 @@ public:
     /// Sets part of the data.
     void set(const glm::vec3& minimum, const MetavoxelData& data, bool blend = false);
 
-    /// Expands the tree, increasing its capacity in all dimensions.
+    /// Expands the tree, doubling its size in all dimensions (that is, increasing its volume eightfold).
     void expand();
 
     void read(Bitstream& in, const MetavoxelLOD& lod = MetavoxelLOD());
@@ -110,6 +121,10 @@ public:
     MetavoxelNode* getRoot(const AttributePointer& attribute) const { return _roots.value(attribute); }
     MetavoxelNode* createRoot(const AttributePointer& attribute);
 
+    /// Performs a deep comparison between this data and the specified other (as opposed to the == operator, which does a
+    /// shallow comparison).
+    bool deepEquals(const MetavoxelData& other, const MetavoxelLOD& lod = MetavoxelLOD()) const;
+
     bool operator==(const MetavoxelData& other) const;
     bool operator!=(const MetavoxelData& other) const;
 
@@ -198,6 +213,14 @@ public:
 
     void clearChildren(const AttributePointer& attribute);
     
+    /// Performs a deep comparison between this and the specified other node.
+    bool deepEquals(const AttributePointer& attribute, const MetavoxelNode& other,
+        const glm::vec3& minimum, float size, const MetavoxelLOD& lod) const;
+
+    /// Retrieves all spanners satisfying the LOD constraint, placing them in the provided set.
+    void getSpanners(const AttributePointer& attribute, const glm::vec3& minimum,
+        float size, const MetavoxelLOD& lod, SharedObjectSet& results) const;
+    
 private:
     Q_DISABLE_COPY(MetavoxelNode)
     
@@ -234,6 +257,9 @@ public:
     /// Encodes a visitation order sequence that visits each child as sorted along the specified direction.
     static int encodeOrder(const glm::vec3& direction);
     
+    /// Returns a random visitation order sequence.
+    static int encodeRandomOrder();
+    
     /// The default visitation order.
     static const int DEFAULT_ORDER;
     
diff --git a/libraries/metavoxels/src/SharedObject.cpp b/libraries/metavoxels/src/SharedObject.cpp
index 05af5f1bf8..d0a1842d31 100644
--- a/libraries/metavoxels/src/SharedObject.cpp
+++ b/libraries/metavoxels/src/SharedObject.cpp
@@ -84,11 +84,19 @@ bool SharedObject::equals(const SharedObject* other, bool sharedAncestry) const
     if (metaObject != other->metaObject() && !sharedAncestry) {
         return false;
     }
-    for (int i = 0; i < metaObject->propertyCount(); i++) {
-        QMetaProperty property = metaObject->property(i);
-        if (property.isStored() && property.read(this) != property.read(other)) {
+    // use the streamer, if we have one
+    const ObjectStreamer* streamer = Bitstream::getObjectStreamer(metaObject);
+    if (streamer) {
+        if (!streamer->equal(this, other)) {
             return false;
         }
+    } else {
+        for (int i = 0; i < metaObject->propertyCount(); i++) {
+            QMetaProperty property = metaObject->property(i);
+            if (property.isStored() && property.read(this) != property.read(other)) {
+                return false;
+            }
+        }
     }
     QList<QByteArray> dynamicPropertyNames = this->dynamicPropertyNames();
     if (dynamicPropertyNames.size() != other->dynamicPropertyNames().size()) {
diff --git a/libraries/models/src/ModelItem.cpp b/libraries/models/src/ModelItem.cpp
index b6f4fe6c1d..8c061102a0 100644
--- a/libraries/models/src/ModelItem.cpp
+++ b/libraries/models/src/ModelItem.cpp
@@ -886,7 +886,19 @@ QScriptValue ModelItemProperties::copyToScriptValue(QScriptEngine* engine) const
     properties.setProperty("shouldDie", _shouldDie);
 
     properties.setProperty("modelURL", _modelURL);
-
+    
+    
+    QScriptValue sittingPoints = engine->newObject();
+    for (int i = 0; i < _sittingPoints.size(); ++i) {
+        QScriptValue sittingPoint = engine->newObject();
+        sittingPoint.setProperty("name", _sittingPoints[i].name);
+        sittingPoint.setProperty("position", vec3toScriptValue(engine, _sittingPoints[i].position));
+        sittingPoint.setProperty("rotation", quatToScriptValue(engine, _sittingPoints[i].rotation));
+        sittingPoints.setProperty(i, sittingPoint);
+    }
+    sittingPoints.setProperty("length", _sittingPoints.size());
+    properties.setProperty("sittingPoints", sittingPoints);
+    
     QScriptValue modelRotation = quatToScriptValue(engine, _modelRotation);
     properties.setProperty("modelRotation", modelRotation);
 
@@ -971,7 +983,7 @@ void ModelItemProperties::copyFromScriptValue(const QScriptValue &object) {
             _modelURLChanged = true;
         }
     }
-
+    
     QScriptValue modelRotation = object.property("modelRotation");
     if (modelRotation.isValid()) {
         QScriptValue x = modelRotation.property("x");
@@ -1125,6 +1137,7 @@ void ModelItemProperties::copyFromModelItem(const ModelItem& modelItem) {
     _animationFrameIndex = modelItem.getAnimationFrameIndex();
     _animationFPS = modelItem.getAnimationFPS();
     _glowLevel = modelItem.getGlowLevel();
+    _sittingPoints = modelItem.getSittingPoints();
 
     _id = modelItem.getID();
     _idSet = true;
diff --git a/libraries/models/src/ModelItem.h b/libraries/models/src/ModelItem.h
index 9a558f2ef4..43aaca48a0 100644
--- a/libraries/models/src/ModelItem.h
+++ b/libraries/models/src/ModelItem.h
@@ -12,9 +12,10 @@
 #ifndef hifi_ModelItem_h
 #define hifi_ModelItem_h
 
-#include <glm/glm.hpp>
 #include <stdint.h>
 
+#include <glm/glm.hpp>
+
 #include <QtScript/QScriptEngine>
 #include <QtCore/QObject>
 
@@ -22,6 +23,8 @@
 #include <CollisionInfo.h>
 #include <SharedUtil.h>
 #include <OctreePacketData.h>
+#include <FBXReader.h>
+
 
 class ModelItem;
 class ModelEditPacketSender;
@@ -122,7 +125,8 @@ private:
     float _animationFrameIndex;
     float _animationFPS;
     float _glowLevel;
-
+    QVector<SittingPoint> _sittingPoints;
+    
     uint32_t _id;
     bool _idSet;
     quint64 _lastEdited;
@@ -211,6 +215,7 @@ public:
     bool hasAnimation() const { return !_animationURL.isEmpty(); }
     const QString& getAnimationURL() const { return _animationURL; }
     float getGlowLevel() const { return _glowLevel; }
+    QVector<SittingPoint> getSittingPoints() const { return _sittingPoints; }
 
     ModelItemID getModelItemID() const { return ModelItemID(getID(), getCreatorTokenID(), getID() != UNKNOWN_MODEL_ID); }
     ModelItemProperties getProperties() const;
@@ -254,6 +259,7 @@ public:
     void setAnimationIsPlaying(bool value) { _animationIsPlaying = value; }
     void setAnimationFPS(float value) { _animationFPS = value; }
     void setGlowLevel(float glowLevel) { _glowLevel = glowLevel; }
+    void setSittingPoints(QVector<SittingPoint> sittingPoints) { _sittingPoints = sittingPoints; }
     
     void setProperties(const ModelItemProperties& properties);
 
@@ -300,6 +306,8 @@ protected:
     QString _modelURL;
     glm::quat _modelRotation;
     
+    QVector<SittingPoint> _sittingPoints;
+    
     float _glowLevel;
 
     uint32_t _creatorTokenID;
diff --git a/libraries/models/src/ModelTree.cpp b/libraries/models/src/ModelTree.cpp
index 466d4c5273..763f0a969e 100644
--- a/libraries/models/src/ModelTree.cpp
+++ b/libraries/models/src/ModelTree.cpp
@@ -117,7 +117,7 @@ void ModelTree::storeModel(const ModelItem& model, const SharedNodePointer& send
     // if we didn't find it in the tree, then store it...
     if (!theOperator.wasFound()) {
         AACube modelCube = model.getAACube();
-        ModelTreeElement* element = (ModelTreeElement*)getOrCreateChildElementContaining(model.getAACube());
+        ModelTreeElement* element = static_cast<ModelTreeElement*>(getOrCreateChildElementContaining(model.getAACube()));
         element->storeModel(model);
         
         // In the case where we stored it, we also need to mark the entire "path" down to the model as
diff --git a/libraries/models/src/ModelTreeElement.cpp b/libraries/models/src/ModelTreeElement.cpp
index 75b9670d0f..960d1dd4cb 100644
--- a/libraries/models/src/ModelTreeElement.cpp
+++ b/libraries/models/src/ModelTreeElement.cpp
@@ -330,6 +330,9 @@ bool ModelTreeElement::updateModel(const ModelItemID& modelID, const ModelItemPr
         }
         if (found) {
             thisModel.setProperties(properties);
+            if (_myTree->getGeometryForModel(thisModel)) {
+                thisModel.setSittingPoints(_myTree->getGeometryForModel(thisModel)->sittingPoints);
+            }
             markWithChangedTime(); // mark our element as changed..
             const bool wantDebug = false;
             if (wantDebug) {
diff --git a/libraries/models/src/ModelsScriptingInterface.cpp b/libraries/models/src/ModelsScriptingInterface.cpp
index 7e08571fe5..bac1213071 100644
--- a/libraries/models/src/ModelsScriptingInterface.cpp
+++ b/libraries/models/src/ModelsScriptingInterface.cpp
@@ -160,10 +160,8 @@ ModelItemID ModelsScriptingInterface::findClosestModel(const glm::vec3& center,
 QVector<ModelItemID> ModelsScriptingInterface::findModels(const glm::vec3& center, float radius) const {
     QVector<ModelItemID> result;
     if (_modelTree) {
-        _modelTree->lockForRead();
         QVector<const ModelItem*> models;
         _modelTree->findModels(center/(float)TREE_SCALE, radius/(float)TREE_SCALE, models);
-        _modelTree->unlock();
 
         foreach (const ModelItem* model, models) {
             ModelItemID thisModelItemID(model->getID(), UNKNOWN_MODEL_TOKEN, true);
diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp
index 95689f8e82..f603d21240 100644
--- a/libraries/networking/src/DomainHandler.cpp
+++ b/libraries/networking/src/DomainHandler.cpp
@@ -11,6 +11,7 @@
 
 #include "NodeList.h"
 #include "PacketHeaders.h"
+#include "UserActivityLogger.h"
 
 #include "DomainHandler.h"
 
@@ -83,6 +84,7 @@ void DomainHandler::setHostname(const QString& hostname) {
         qDebug("Looking up DS hostname %s.", _hostname.toLocal8Bit().constData());
         QHostInfo::lookupHost(_hostname, this, SLOT(completedHostnameLookup(const QHostInfo&)));
         
+        UserActivityLogger::getInstance().changedDomain(_hostname);
         emit hostnameChanged(_hostname);
     }
 }
diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp
index e2bc46b3be..a5c05a6ae9 100644
--- a/libraries/networking/src/PacketHeaders.cpp
+++ b/libraries/networking/src/PacketHeaders.cpp
@@ -50,6 +50,8 @@ PacketVersion versionForPacketType(PacketType type) {
         case PacketTypeMicrophoneAudioNoEcho:
         case PacketTypeMicrophoneAudioWithEcho:
         case PacketTypeSilentAudioFrame:
+            return 2;
+        case PacketTypeMixedAudio:
             return 1;
         case PacketTypeAvatarData:
             return 3;
diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h
index 0f87b0e607..83350a32d1 100644
--- a/libraries/networking/src/PacketHeaders.h
+++ b/libraries/networking/src/PacketHeaders.h
@@ -40,7 +40,7 @@ enum PacketType {
     PacketTypeCreateAssignment,
     PacketTypeDomainOAuthRequest,
     PacketTypeMuteEnvironment,
-    PacketTypeDataServerSend, // reusable
+    PacketTypeAudioStreamStats,
     PacketTypeDataServerConfirm,
     PacketTypeVoxelQuery,
     PacketTypeVoxelData,
diff --git a/libraries/networking/src/SequenceNumberStats.cpp b/libraries/networking/src/SequenceNumberStats.cpp
new file mode 100644
index 0000000000..15d3c0542e
--- /dev/null
+++ b/libraries/networking/src/SequenceNumberStats.cpp
@@ -0,0 +1,184 @@
+//
+//  SequenceNumberStats.cpp
+//  libraries/networking/src
+//
+//  Created by Yixin Wang on 6/25/2014
+//  Copyright 2014 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
+//
+
+#include "SequenceNumberStats.h"
+
+#include <limits>
+
+SequenceNumberStats::SequenceNumberStats()
+    : _lastReceived(std::numeric_limits<quint16>::max()),
+    _missingSet(),
+    _numReceived(0),
+    _numUnreasonable(0),
+    _numEarly(0),
+    _numLate(0),
+    _numLost(0),
+    _numRecovered(0),
+    _numDuplicate(0),
+    _lastSenderUUID()
+{
+}
+
+void SequenceNumberStats::reset() {
+    _missingSet.clear();
+    _numReceived = 0;
+    _numUnreasonable = 0;
+    _numEarly = 0;
+    _numLate = 0;
+    _numLost = 0;
+    _numRecovered = 0;
+    _numDuplicate = 0;
+}
+
+static const int UINT16_RANGE = std::numeric_limits<uint16_t>::max() + 1;
+static const int MAX_REASONABLE_SEQUENCE_GAP = 1000;  // this must be less than UINT16_RANGE / 2 for rollover handling to work
+
+void SequenceNumberStats::sequenceNumberReceived(quint16 incoming, QUuid senderUUID, const bool wantExtraDebugging) {
+
+    // if the sender node has changed, reset all stats
+    if (senderUUID != _lastSenderUUID) {
+        qDebug() << "sequence number stats was reset due to new sender node";
+        qDebug() << "previous:" << _lastSenderUUID << "current:" << senderUUID;
+        reset();
+        _lastSenderUUID = senderUUID;
+    }
+
+    // determine our expected sequence number... handle rollover appropriately
+    quint16 expected = _numReceived > 0 ? _lastReceived + (quint16)1 : incoming;
+
+    _numReceived++;
+
+    if (incoming == expected) { // on time
+        _lastReceived = incoming;
+    } else { // out of order
+
+        if (wantExtraDebugging) {
+            qDebug() << "out of order... got:" << incoming << "expected:" << expected;
+        }
+
+        int incomingInt = (int)incoming;
+        int expectedInt = (int)expected;
+
+        // check if the gap between incoming and expected is reasonable, taking possible rollover into consideration
+        int absGap = std::abs(incomingInt - expectedInt);
+        if (absGap >= UINT16_RANGE - MAX_REASONABLE_SEQUENCE_GAP) {
+            // rollover likely occurred between incoming and expected.
+            // correct the larger of the two so that it's within [-UINT16_RANGE, -1] while the other remains within [0, UINT16_RANGE-1]
+            if (incomingInt > expectedInt) {
+                incomingInt -= UINT16_RANGE;
+            } else {
+                expectedInt -= UINT16_RANGE;
+            }
+        } else if (absGap > MAX_REASONABLE_SEQUENCE_GAP) {
+            // ignore packet if gap is unreasonable
+            qDebug() << "ignoring unreasonable sequence number:" << incoming
+                << "previous:" << _lastReceived;
+            _numUnreasonable++;
+            return;
+        }
+
+        // now that rollover has been corrected for (if it occurred), incoming and expected can be
+        // compared to each other directly, though one of them might be negative
+        if (incomingInt > expectedInt) { // early
+            if (wantExtraDebugging) {
+                qDebug() << "this packet is earlier than expected...";
+                qDebug() << ">>>>>>>> missing gap=" << (incomingInt - expectedInt);
+            }
+
+            _numEarly++;
+            _numLost += (incomingInt - expectedInt);
+
+            // add all sequence numbers that were skipped to the missing sequence numbers list
+            for (int missingInt = expectedInt; missingInt < incomingInt; missingInt++) {
+                _missingSet.insert((quint16)(missingInt < 0 ? missingInt + UINT16_RANGE : missingInt));
+            }
+            
+            // prune missing sequence list if it gets too big; sequence numbers that are older than MAX_REASONABLE_SEQUENCE_GAP
+            // will be removed.
+            if (_missingSet.size() > MAX_REASONABLE_SEQUENCE_GAP) {
+                pruneMissingSet(wantExtraDebugging);
+            }
+
+            _lastReceived = incoming;
+        } else { // late
+            if (wantExtraDebugging) {
+                qDebug() << "this packet is later than expected...";
+            }
+            _numLate++;
+
+            // remove this from missing sequence number if it's in there
+            if (_missingSet.remove(incoming)) {
+                if (wantExtraDebugging) {
+                    qDebug() << "found it in _missingSet";
+                }
+                _numLost--;
+                _numRecovered++;
+            } else {
+                if (wantExtraDebugging) {
+                    qDebug() << "sequence:" << incoming << "was NOT found in _missingSet and is probably a duplicate";
+                }
+                _numDuplicate++;
+            }
+
+            // do not update _incomingLastSequence; it shouldn't become smaller
+        }
+    }
+}
+
+void SequenceNumberStats::pruneMissingSet(const bool wantExtraDebugging) {
+    if (wantExtraDebugging) {
+        qDebug() << "pruning _missingSet! size:" << _missingSet.size();
+    }
+
+    // some older sequence numbers may be from before a rollover point; this must be handled.
+    // some sequence numbers in this list may be larger than _incomingLastSequence, indicating that they were received
+    // before the most recent rollover.
+    int cutoff = (int)_lastReceived - MAX_REASONABLE_SEQUENCE_GAP;
+    if (cutoff >= 0) {
+        quint16 nonRolloverCutoff = (quint16)cutoff;
+        QSet<quint16>::iterator i = _missingSet.begin();
+        while (i != _missingSet.end()) {
+            quint16 missing = *i;
+            if (wantExtraDebugging) {
+                qDebug() << "checking item:" << missing << "is it in need of pruning?";
+                qDebug() << "old age cutoff:" << nonRolloverCutoff;
+            }
+
+            if (missing > _lastReceived || missing < nonRolloverCutoff) {
+                i = _missingSet.erase(i);
+                if (wantExtraDebugging) {
+                    qDebug() << "pruning really old missing sequence:" << missing;
+                }
+            } else {
+                i++;
+            }
+        }
+    } else {
+        quint16 rolloverCutoff = (quint16)(cutoff + UINT16_RANGE);
+        QSet<quint16>::iterator i = _missingSet.begin();
+        while (i != _missingSet.end()) {
+            quint16 missing = *i;
+            if (wantExtraDebugging) {
+                qDebug() << "checking item:" << missing << "is it in need of pruning?";
+                qDebug() << "old age cutoff:" << rolloverCutoff;
+            }
+
+            if (missing > _lastReceived && missing < rolloverCutoff) {
+                i = _missingSet.erase(i);
+                if (wantExtraDebugging) {
+                    qDebug() << "pruning really old missing sequence:" << missing;
+                }
+            } else {
+                i++;
+            }
+        }
+    }
+}
diff --git a/libraries/networking/src/SequenceNumberStats.h b/libraries/networking/src/SequenceNumberStats.h
new file mode 100644
index 0000000000..b2561552ef
--- /dev/null
+++ b/libraries/networking/src/SequenceNumberStats.h
@@ -0,0 +1,53 @@
+//
+//  SequenceNumberStats.h
+//  libraries/networking/src
+//
+//  Created by Yixin Wang on 6/25/2014
+//  Copyright 2014 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
+//
+
+#ifndef hifi_SequenceNumberStats_h
+#define hifi_SequenceNumberStats_h
+
+#include "SharedUtil.h"
+#include <quuid.h>
+
+class SequenceNumberStats {
+public:
+    SequenceNumberStats();
+
+    void reset();
+
+    void sequenceNumberReceived(quint16 incoming, QUuid senderUUID = QUuid(), const bool wantExtraDebugging = false);
+
+    quint32 getNumReceived() const { return _numReceived; }
+    quint32 getNumUnreasonable() const { return _numUnreasonable; }
+    quint32 getNumOutOfOrder() const { return _numEarly + _numLate; }
+    quint32 getNumEarly() const { return _numEarly; }
+    quint32 getNumLate() const { return _numLate; }
+    quint32 getNumLost() const { return _numLost; }
+    quint32 getNumRecovered() const { return _numRecovered; }
+    quint32 getNumDuplicate() const { return _numDuplicate; }
+    const QSet<quint16>& getMissingSet() const { return _missingSet; }
+
+private:
+    void pruneMissingSet(const bool wantExtraDebugging);
+
+    quint16 _lastReceived;
+    QSet<quint16> _missingSet;
+
+    quint32 _numReceived;
+    quint32 _numUnreasonable;
+    quint32 _numEarly;
+    quint32 _numLate;
+    quint32 _numLost;
+    quint32 _numRecovered;
+    quint32 _numDuplicate;
+
+    QUuid _lastSenderUUID;
+};
+
+#endif // hifi_SequenceNumberStats_h
diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp
new file mode 100644
index 0000000000..aa18cb43ee
--- /dev/null
+++ b/libraries/networking/src/UserActivityLogger.cpp
@@ -0,0 +1,155 @@
+//
+//  UserActivityLogger.cpp
+//
+//
+//  Created by Clement on 5/21/14.
+//  Copyright 2014 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
+//
+
+#include "UserActivityLogger.h"
+
+#include <QEventLoop>
+#include <QJsonDocument>
+#include <QHttpMultiPart>
+#include <QTimer>
+
+static const QString USER_ACTIVITY_URL = "/api/v1/user_activities";
+
+UserActivityLogger& UserActivityLogger::getInstance() {
+    static UserActivityLogger sharedInstance;
+    return sharedInstance;
+}
+
+UserActivityLogger::UserActivityLogger() {
+}
+
+void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCallbackParameters params) {
+    AccountManager& accountManager = AccountManager::getInstance();
+    QHttpMultiPart* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
+    
+    // Adding the action name
+    QHttpPart actionPart;
+    actionPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"action_name\"");
+    actionPart.setBody(QByteArray().append(action));
+    multipart->append(actionPart);
+    
+    // If there are action details, add them to the multipart
+    if (!details.isEmpty()) {
+        QHttpPart detailsPart;
+        detailsPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data;"
+                              " name=\"action_details\"");
+        detailsPart.setBody(QJsonDocument(details).toJson(QJsonDocument::Compact));
+        multipart->append(detailsPart);
+    }
+    qDebug() << "Logging activity" << action;
+    
+    // if no callbacks specified, call our owns
+    if (params.isEmpty()) {
+        params.jsonCallbackReceiver = this;
+        params.jsonCallbackMethod = "requestFinished";
+        params.errorCallbackReceiver = this;
+        params.errorCallbackMethod = "requestError";
+    }
+    
+    accountManager.authenticatedRequest(USER_ACTIVITY_URL,
+                                        QNetworkAccessManager::PostOperation,
+                                        params,
+                                        NULL,
+                                        multipart);
+}
+
+void UserActivityLogger::requestFinished(const QJsonObject& object) {
+    qDebug() << object;
+}
+
+void UserActivityLogger::requestError(QNetworkReply::NetworkError error,const QString& string) {
+    qDebug() << error << ": " << string;
+}
+
+void UserActivityLogger::launch(QString applicationVersion) {
+    const QString ACTION_NAME = "launch";
+    QJsonObject actionDetails;
+    QString VERSION_KEY = "version";
+    actionDetails.insert(VERSION_KEY, applicationVersion);
+    
+    logAction(ACTION_NAME, actionDetails);
+}
+
+void UserActivityLogger::close(int delayTime) {
+    const QString ACTION_NAME = "close";
+    
+    // In order to get the end of the session, we need to give the account manager enough time to send the packet.
+    QEventLoop loop;
+    // Here we connect the callbacks to stop the event loop
+    JSONCallbackParameters params;
+    params.jsonCallbackReceiver = &loop;
+    params.errorCallbackReceiver = &loop;
+    params.jsonCallbackMethod = "quit";
+    params.errorCallbackMethod = "quit";
+    // In case something goes wrong, we also setup a timer so that the delai is not greater than delayTime
+    QTimer timer;
+    connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
+    // Now we can log it
+    logAction(ACTION_NAME, QJsonObject(), params);
+    timer.start(delayTime);
+    loop.exec();
+}
+
+void UserActivityLogger::changedDisplayName(QString displayName) {
+    const QString ACTION_NAME = "changed_display_name";
+    QJsonObject actionDetails;
+    const QString DISPLAY_NAME = "display_name";
+    
+    actionDetails.insert(DISPLAY_NAME, displayName);
+    
+    logAction(ACTION_NAME, actionDetails);
+}
+
+void UserActivityLogger::changedModel(QString typeOfModel, QString modelURL) {
+    const QString ACTION_NAME = "changed_model";
+    QJsonObject actionDetails;
+    const QString TYPE_OF_MODEL = "type_of_model";
+    const QString MODEL_URL = "model_url";
+    
+    actionDetails.insert(TYPE_OF_MODEL, typeOfModel);
+    actionDetails.insert(MODEL_URL, modelURL);
+    
+    logAction(ACTION_NAME, actionDetails);
+}
+
+void UserActivityLogger::changedDomain(QString domainURL) {
+    const QString ACTION_NAME = "changed_domain";
+    QJsonObject actionDetails;
+    const QString DOMAIN_URL = "domain_url";
+    
+    actionDetails.insert(DOMAIN_URL, domainURL);
+    
+    logAction(ACTION_NAME, actionDetails);
+}
+
+void UserActivityLogger::connectedDevice(QString typeOfDevice, QString deviceName) {
+    const QString ACTION_NAME = "connected_device";
+    QJsonObject actionDetails;
+    const QString TYPE_OF_DEVICE = "type_of_device";
+    const QString DEVICE_NAME = "device_name";
+    
+    actionDetails.insert(TYPE_OF_DEVICE, typeOfDevice);
+    actionDetails.insert(DEVICE_NAME, deviceName);
+    
+    logAction(ACTION_NAME, actionDetails);
+
+}
+
+void UserActivityLogger::loadedScript(QString scriptName) {
+    const QString ACTION_NAME = "loaded_script";
+    QJsonObject actionDetails;
+    const QString SCRIPT_NAME = "script_name";
+    
+    actionDetails.insert(SCRIPT_NAME, scriptName);
+    
+    logAction(ACTION_NAME, actionDetails);
+
+}
diff --git a/libraries/networking/src/UserActivityLogger.h b/libraries/networking/src/UserActivityLogger.h
new file mode 100644
index 0000000000..4823143234
--- /dev/null
+++ b/libraries/networking/src/UserActivityLogger.h
@@ -0,0 +1,47 @@
+//
+//  UserActivityLogger.h
+//
+//
+//  Created by Clement on 5/21/14.
+//  Copyright 2014 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
+//
+
+#ifndef hifi_UserActivityLogger_h
+#define hifi_UserActivityLogger_h
+
+#include "AccountManager.h"
+
+#include <QObject>
+#include <QString>
+#include <QJsonObject>
+#include <QNetworkReply>
+
+class UserActivityLogger : public QObject {
+    Q_OBJECT
+    
+public:
+    static UserActivityLogger& getInstance();
+    
+public slots:
+    void logAction(QString action, QJsonObject details = QJsonObject(), JSONCallbackParameters params = JSONCallbackParameters());
+    
+    void launch(QString applicationVersion);
+    void close(int delayTime);
+    void changedDisplayName(QString displayName);
+    void changedModel(QString typeOfModel, QString modelURL);
+    void changedDomain(QString domainURL);
+    void connectedDevice(QString typeOfDevice, QString deviceName);
+    void loadedScript(QString scriptName);
+    
+private slots:
+    void requestFinished(const QJsonObject& object);
+    void requestError(QNetworkReply::NetworkError error,const QString& string);
+    
+private:
+    UserActivityLogger();
+};
+
+#endif // hifi_UserActivityLogger_h
\ No newline at end of file
diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp
index cbdc4753dc..2af86663f7 100644
--- a/libraries/octree/src/Octree.cpp
+++ b/libraries/octree/src/Octree.cpp
@@ -757,7 +757,7 @@ bool findShapeCollisionsOp(OctreeElement* element, void* extraData) {
     // coarse check against bounds
     AACube cube = element->getAACube();
     cube.scale(TREE_SCALE);
-    if (!cube.expandedContains(args->shape->getPosition(), args->shape->getBoundingRadius())) {
+    if (!cube.expandedContains(args->shape->getTranslation(), args->shape->getBoundingRadius())) {
         return false;
     }
     if (!element->isLeaf()) {
diff --git a/libraries/octree/src/OctreeEditPacketSender.cpp b/libraries/octree/src/OctreeEditPacketSender.cpp
index 43e253b2da..d67306e8c7 100644
--- a/libraries/octree/src/OctreeEditPacketSender.cpp
+++ b/libraries/octree/src/OctreeEditPacketSender.cpp
@@ -354,7 +354,7 @@ void OctreeEditPacketSender::processNackPacket(const QByteArray& packet) {
     // read number of sequence numbers
     uint16_t numSequenceNumbers = (*(uint16_t*)dataAt);
     dataAt += sizeof(uint16_t);
-
+    
     // read sequence numbers and queue packets for resend
     for (int i = 0; i < numSequenceNumbers; i++) {
         unsigned short int sequenceNumber = (*(unsigned short int*)dataAt);
diff --git a/libraries/octree/src/OctreeSceneStats.cpp b/libraries/octree/src/OctreeSceneStats.cpp
index 28445ec327..e585c8dfe6 100644
--- a/libraries/octree/src/OctreeSceneStats.cpp
+++ b/libraries/octree/src/OctreeSceneStats.cpp
@@ -21,10 +21,6 @@
 #include "OctreeSceneStats.h"
 
 
-const uint16_t MAX_MISSING_SEQUENCE = 100; /// how many items in our _missingSequenceNumbers before we start to prune them
-const uint16_t MAX_MISSING_SEQUENCE_OLD_AGE = 1000; /// age we allow items in _missingSequenceNumbers to be before pruning
-
-
 const int samples = 100;
 OctreeSceneStats::OctreeSceneStats() : 
     _isReadyToSend(false),
@@ -39,14 +35,7 @@ OctreeSceneStats::OctreeSceneStats() :
     _incomingPacket(0),
     _incomingBytes(0),
     _incomingWastedBytes(0),
-    _incomingLastSequence(0),
-    _incomingLikelyLost(0),
-    _incomingRecovered(0),
-    _incomingEarly(0),
-    _incomingLate(0),
-    _incomingReallyLate(0),
-    _incomingPossibleDuplicate(0),
-    _missingSequenceNumbers(),
+    _incomingOctreeSequenceNumberStats(),
     _incomingFlightTimeAverage(samples),
     _jurisdictionRoot(NULL)
 {
@@ -150,15 +139,8 @@ void OctreeSceneStats::copyFromOther(const OctreeSceneStats& other) {
     _incomingPacket = other._incomingPacket;
     _incomingBytes = other._incomingBytes;
     _incomingWastedBytes = other._incomingWastedBytes;
-    _incomingLastSequence = other._incomingLastSequence;
-    _incomingLikelyLost = other._incomingLikelyLost;
-    _incomingRecovered = other._incomingRecovered;
-    _incomingEarly = other._incomingEarly;
-    _incomingLate = other._incomingLate;
-    _incomingReallyLate = other._incomingReallyLate;
-    _incomingPossibleDuplicate = other._incomingPossibleDuplicate;
-    
-    _missingSequenceNumbers = other._missingSequenceNumbers;
+
+    _incomingOctreeSequenceNumberStats = other._incomingOctreeSequenceNumberStats;
 }
 
 
@@ -875,155 +857,8 @@ void OctreeSceneStats::trackIncomingOctreePacket(const QByteArray& packet,
         qDebug() << "ignoring unreasonable packet... flightTime:" << flightTime;
         return; // ignore any packets that are unreasonable
     }
-
-    const int UINT16_RANGE = std::numeric_limits<uint16_t>::max() + 1;
-
-    // determine our expected sequence number... handle rollover appropriately
-    OCTREE_PACKET_SEQUENCE expected = _incomingPacket > 0 ? _incomingLastSequence + (quint16)1 : sequence;
-
-    const int USECS_PER_MSEC = 1000;
-    float flightTimeMsecs = flightTime / USECS_PER_MSEC;
-    _incomingFlightTimeAverage.updateAverage(flightTimeMsecs);
-
-    // track out of order and possibly lost packets...
-    if (sequence == _incomingLastSequence) {
-        if (wantExtraDebugging) {
-            qDebug() << "last packet duplicate got:" << sequence << "_incomingLastSequence:" << _incomingLastSequence;
-        }
-    } else {
-        if (sequence != expected) {
-            if (wantExtraDebugging) {
-                qDebug() << "out of order... got:" << sequence << "expected:" << expected;
-            }
-
-            int sequenceInt = (int)sequence;
-            int expectedInt = (int)expected;
-
-            // if distance between sequence and expected are more than half of the total range of possible seq numbers,
-            // assume that a rollover occurred between the two.
-            // correct the larger one so it's in the range [-UINT16_RANGE, -1] while the other remains in [0, UINT16_RANGE-1]
-            // after doing so, sequenceInt and expectedInt can be correctly compared to each other, though one may be negative
-            if (std::abs(sequenceInt - expectedInt) > UINT16_RANGE / 2) {
-                if (sequenceInt > expectedInt) {
-                    sequenceInt -= UINT16_RANGE;
-                }
-                else {
-                    expectedInt -= UINT16_RANGE;
-                }
-            }
-
-            // Guard against possible corrupted packets... with bad sequence numbers
-            const int MAX_RESONABLE_SEQUENCE_OFFSET = 2000;
-            const int MIN_RESONABLE_SEQUENCE_OFFSET = -2000;
-
-            int sequenceOffset = (sequenceInt - expectedInt);
-            if (sequenceOffset > MAX_RESONABLE_SEQUENCE_OFFSET || sequenceOffset < MIN_RESONABLE_SEQUENCE_OFFSET) {
-                qDebug() << "ignoring unreasonable packet... sequence:" << sequence << "_incomingLastSequence:" << _incomingLastSequence;
-                return; // ignore any packets that are unreasonable
-            }
-
-            // if the sequence is less than our expected, then this might be a packet
-            // that was delayed and so we should find it in our lostSequence list
-            if (sequenceInt < expectedInt) {
-
-                // if no rollover between them: sequenceInt, expectedInt are both in range [0, UINT16_RANGE-1]
-                // if rollover between them: sequenceInt in [-UINT16_RANGE, -1], expectedInt in [0, UINT16_RANGE-1]
-
-                if (wantExtraDebugging) {
-                    qDebug() << "this packet is later than expected...";
-                }
-                if (sequenceInt < expectedInt - MAX_MISSING_SEQUENCE_OLD_AGE) {
-                    _incomingReallyLate++;
-                }
-                else {
-                    _incomingLate++;
-                }
-
-                if (_missingSequenceNumbers.contains(sequence)) {
-                    if (wantExtraDebugging) {
-                        qDebug() << "found it in _missingSequenceNumbers";
-                    }
-                    _missingSequenceNumbers.remove(sequence);
-                    _incomingLikelyLost--;
-                    _incomingRecovered++;
-                }
-                else {
-                    // if we're still in our pruning window, and we didn't find it in our missing list,
-                    // than this is really unexpected and can probably only happen if the packet was a
-                    // duplicate
-                    if (sequenceInt >= expectedInt - MAX_MISSING_SEQUENCE_OLD_AGE) {
-                        if (wantExtraDebugging) {
-                            qDebug() << "sequence:" << sequence << "WAS NOT found in _missingSequenceNumbers, and not that old... (expected - MAX_MISSING_SEQUENCE_OLD_AGE):"
-                                << (uint16_t)(expectedInt - MAX_MISSING_SEQUENCE_OLD_AGE);
-                        }
-                        _incomingPossibleDuplicate++;
-                    }
-                }
-
-                // don't update _incomingLastSequence in this case.
-                // only bump the last sequence if it was greater than our expected sequence, this will keep us from
-                // accidentally going backwards when an out of order (recovered) packet comes in
-
-            } else {    // sequenceInt > expectedInt
-
-                // if no rollover between them: sequenceInt, expectedInt are both in range [0, UINT16_RANGE-1]
-                // if rollover between them: sequenceInt in [0, UINT16_RANGE-1], expectedInt in [-UINT16_RANGE, -1]
-
-                if (wantExtraDebugging) {
-                    qDebug() << "this packet is earlier than expected...";
-                }
-                _incomingEarly++;
-
-                // hmm... so, we either didn't get some packets, or this guy came early...
-                int missing = sequenceInt - expectedInt;
-                if (wantExtraDebugging) {
-                    qDebug() << ">>>>>>>> missing gap=" << missing;
-                }
-                _incomingLikelyLost += missing;
-                for (int missingSequenceInt = expectedInt; missingSequenceInt < sequenceInt; missingSequenceInt++) {
-                    OCTREE_PACKET_SEQUENCE missingSequence = missingSequenceInt >= 0 ? missingSequenceInt : missingSequenceInt + UINT16_RANGE;
-                    _missingSequenceNumbers << missingSequence;
-                }
-
-                _incomingLastSequence = sequence;
-            }
-        } else {    // sequence = expected
-
-            _incomingLastSequence = sequence;
-        }
-    }
-
-    // do some garbage collecting on our _missingSequenceNumbers
-    if (_missingSequenceNumbers.size() > MAX_MISSING_SEQUENCE) {
-        if (wantExtraDebugging) {
-            qDebug() << "too many _missingSequenceNumbers:" << _missingSequenceNumbers.size();
-        }
-
-        int oldAgeCutoff = (int)_incomingLastSequence - MAX_MISSING_SEQUENCE_OLD_AGE;
-
-        foreach(uint16_t missingItem, _missingSequenceNumbers) {
-            if (wantExtraDebugging) {
-                qDebug() << "checking item:" << missingItem << "is it in need of pruning?";
-                qDebug() << "(_incomingLastSequence - MAX_MISSING_SEQUENCE_OLD_AGE):"
-                    << (uint16_t)((int)_incomingLastSequence - MAX_MISSING_SEQUENCE_OLD_AGE);
-            }
-
-            bool prune;
-            if (oldAgeCutoff >= 0) {
-                prune = (missingItem <= oldAgeCutoff || missingItem > _incomingLastSequence);
-            }
-            else {
-                prune = (missingItem <= oldAgeCutoff + UINT16_RANGE && missingItem > _incomingLastSequence);
-            }
-
-            if (prune) {
-                if (wantExtraDebugging) {
-                    qDebug() << "pruning really old missing sequence:" << missingItem;
-                }
-                _missingSequenceNumbers.remove(missingItem);
-            }
-        }
-    }
+    
+    _incomingOctreeSequenceNumberStats.sequenceNumberReceived(sequence);
 
     // track packets here...
     _incomingPacket++;
diff --git a/libraries/octree/src/OctreeSceneStats.h b/libraries/octree/src/OctreeSceneStats.h
index 1c468a8dc6..d7b65c63be 100644
--- a/libraries/octree/src/OctreeSceneStats.h
+++ b/libraries/octree/src/OctreeSceneStats.h
@@ -17,6 +17,7 @@
 #include <SharedUtil.h>
 #include "JurisdictionMap.h"
 #include "OctreePacketData.h"
+#include "SequenceNumberStats.h"
 
 #define GREENISH  0x40ff40d0
 #define YELLOWISH 0xffef40c0
@@ -164,16 +165,9 @@ public:
     quint32 getIncomingPackets() const { return _incomingPacket; }
     quint64 getIncomingBytes() const { return _incomingBytes; } 
     quint64 getIncomingWastedBytes() const { return _incomingWastedBytes; }
-    quint32 getIncomingOutOfOrder() const { return _incomingLate + _incomingEarly; }
-    quint32 getIncomingLikelyLost() const { return _incomingLikelyLost; }
-    quint32 getIncomingRecovered() const { return _incomingRecovered; }
-    quint32 getIncomingEarly() const { return _incomingEarly; }
-    quint32 getIncomingLate() const { return _incomingLate; }
-    quint32 getIncomingReallyLate() const { return _incomingReallyLate; }
-    quint32 getIncomingPossibleDuplicate() const { return _incomingPossibleDuplicate; }
     float getIncomingFlightTimeAverage() { return _incomingFlightTimeAverage.getAverage(); }
     
-    const QSet<OCTREE_PACKET_SEQUENCE>& getMissingSequenceNumbers() const { return _missingSequenceNumbers; }
+    const SequenceNumberStats& getIncomingOctreeSequenceNumberStats() const { return _incomingOctreeSequenceNumberStats; }
 
 private:
 
@@ -268,14 +262,8 @@ private:
     quint64 _incomingBytes;
     quint64 _incomingWastedBytes;
 
-    quint16 _incomingLastSequence; /// last incoming sequence number
-    quint32 _incomingLikelyLost; /// count of packets likely lost, may be off by _incomingReallyLate count
-    quint32 _incomingRecovered; /// packets that were late, and we had in our missing list, we consider recovered
-    quint32 _incomingEarly; /// out of order earlier than expected
-    quint32 _incomingLate; /// out of order later than expected
-    quint32 _incomingReallyLate; /// out of order and later than MAX_MISSING_SEQUENCE_OLD_AGE late
-    quint32 _incomingPossibleDuplicate; /// out of order possibly a duplicate
-    QSet<OCTREE_PACKET_SEQUENCE> _missingSequenceNumbers;
+    SequenceNumberStats _incomingOctreeSequenceNumberStats;
+
     SimpleMovingAverage _incomingFlightTimeAverage;
     
     // features related items
diff --git a/libraries/particles/src/ParticleCollisionSystem.cpp b/libraries/particles/src/ParticleCollisionSystem.cpp
index 358c5a1b84..d8d5887d97 100644
--- a/libraries/particles/src/ParticleCollisionSystem.cpp
+++ b/libraries/particles/src/ParticleCollisionSystem.cpp
@@ -202,14 +202,13 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) {
     foreach (const AvatarSharedPointer& avatarPointer, _avatars->getAvatarHash()) {
         AvatarData* avatar = avatarPointer.data();
 
-        // use a very generous bounding radius since the arms can stretch
-        float totalRadius = 2.f * avatar->getBoundingRadius() + radius;
+        float totalRadius = avatar->getBoundingRadius() + radius;
         glm::vec3 relativePosition = center - avatar->getPosition();
         if (glm::dot(relativePosition, relativePosition) > (totalRadius * totalRadius)) {
             continue;
         }
 
-        if (avatar->findParticleCollisions(center, radius, _collisions)) {
+        if (avatar->findSphereCollisions(center, radius, _collisions)) {
             int numCollisions = _collisions.size();
             for (int i = 0; i < numCollisions; ++i) {
                 CollisionInfo* collision = _collisions.getCollision(i);
@@ -222,25 +221,6 @@ void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) {
                 if (glm::dot(relativeVelocity, collision->_penetration) <= 0.f) {
                     // only collide when particle and collision point are moving toward each other
                     // (doing this prevents some "collision snagging" when particle penetrates the object)
-    
-                    // HACK BEGIN: to allow paddle hands to "hold" particles we attenuate soft collisions against them.
-                    if (collision->_type == COLLISION_TYPE_PADDLE_HAND) {
-                        // NOTE: the physics are wrong (particles cannot roll) but it IS possible to catch a slow moving particle.
-                        // TODO: make this less hacky when we have more per-collision details
-                        float elasticity = ELASTICITY;
-                        float attenuationFactor = glm::length(collision->_addedVelocity) / HALTING_SPEED;
-                        float damping = DAMPING;
-                        if (attenuationFactor < 1.f) {
-                            collision->_addedVelocity *= attenuationFactor;
-                            elasticity *= attenuationFactor;
-                            // NOTE: the math below keeps the damping piecewise continuous,
-                            // while ramping it up to 1 when attenuationFactor = 0
-                            damping = DAMPING + (1.f - attenuationFactor) * (1.f - DAMPING);
-                        }
-                        collision->_damping = damping;
-                    }
-                    // HACK END
-    
                     updateCollisionSound(particle, collision->_penetration, COLLISION_FREQUENCY);
                     collision->_penetration /= (float)(TREE_SCALE);
                     particle->applyHardCollision(*collision);
diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp
index 28b8f289e5..38cf8f0b09 100644
--- a/libraries/script-engine/src/ScriptEngine.cpp
+++ b/libraries/script-engine/src/ScriptEngine.cpp
@@ -323,8 +323,9 @@ void ScriptEngine::evaluate() {
 
     if (_engine.hasUncaughtException()) {
         int line = _engine.uncaughtExceptionLineNumber();
-        qDebug() << "Uncaught exception at line" << line << ":" << result.toString();
-        emit errorMessage("Uncaught exception at line" + QString::number(line) + ":" + result.toString());
+        qDebug() << "Uncaught exception at (" << _fileNameString << ") line" << line << ":" << result.toString();
+        emit errorMessage("Uncaught exception at (" + _fileNameString + ") line" + QString::number(line) + ":" + result.toString());
+        _engine.clearExceptions();
     }
 }
 
@@ -333,7 +334,7 @@ QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileN
     bool hasUncaughtException = _engine.hasUncaughtException();
     if (hasUncaughtException) {
         int line = _engine.uncaughtExceptionLineNumber();
-        qDebug() << "Uncaught exception at line" << line << ": " << result.toString();
+        qDebug() << "Uncaught exception at (" << _fileNameString << ") line" << line << ": " << result.toString();
     }
     emit evaluationFinished(result, hasUncaughtException);
     _engine.clearExceptions();
@@ -357,14 +358,15 @@ void ScriptEngine::run() {
         init();
     }
     _isRunning = true;
+    _isFinished = false;
     emit runningStateChanged();
 
     QScriptValue result = _engine.evaluate(_scriptContents);
     if (_engine.hasUncaughtException()) {
         int line = _engine.uncaughtExceptionLineNumber();
-
-        qDebug() << "Uncaught exception at line" << line << ":" << result.toString();
-        emit errorMessage("Uncaught exception at line" + QString::number(line) + ":" + result.toString());
+        qDebug() << "Uncaught exception at (" << _fileNameString << ") line" << line << ":" << result.toString();
+        emit errorMessage("Uncaught exception at (" + _fileNameString + ") line" + QString::number(line) + ":" + result.toString());
+        _engine.clearExceptions();
     }
 
     QElapsedTimer startTime;
@@ -467,13 +469,17 @@ void ScriptEngine::run() {
                         _numAvatarSoundSentBytes = 0;
                     }
                 }
-
+                
                 QByteArray audioPacket = byteArrayWithPopulatedHeader(silentFrame
                                                                       ? PacketTypeSilentAudioFrame
                                                                       : PacketTypeMicrophoneAudioNoEcho);
 
                 QDataStream packetStream(&audioPacket, QIODevice::Append);
 
+                // pack a placeholder value for sequence number for now, will be packed when destination node is known
+                int numPreSequenceNumberBytes = audioPacket.size();
+                packetStream << (quint16)0;
+
                 // use the orientation and position of this avatar for the source of this audio
                 packetStream.writeRawData(reinterpret_cast<const char*>(&_avatarData->getPosition()), sizeof(glm::vec3));
                 glm::quat headOrientation = _avatarData->getHeadOrientation();
@@ -493,7 +499,19 @@ void ScriptEngine::run() {
                                               numAvailableSamples * sizeof(int16_t));
                 }
 
-                nodeList->broadcastToNodes(audioPacket, NodeSet() << NodeType::AudioMixer);
+                // write audio packet to AudioMixer nodes
+                NodeList* nodeList = NodeList::getInstance();
+                foreach(const SharedNodePointer& node, nodeList->getNodeHash()) {
+                    // only send to nodes of type AudioMixer
+                    if (node->getType() == NodeType::AudioMixer) {
+                        // pack sequence number
+                        quint16 sequence = _outgoingScriptAudioSequenceNumbers[node->getUUID()]++;
+                        memcpy(audioPacket.data() + numPreSequenceNumberBytes, &sequence, sizeof(quint16));
+
+                        // send audio packet
+                        nodeList->writeDatagram(audioPacket, node);
+                    }
+                }
             }
         }
 
@@ -504,8 +522,9 @@ void ScriptEngine::run() {
 
         if (_engine.hasUncaughtException()) {
             int line = _engine.uncaughtExceptionLineNumber();
-            qDebug() << "Uncaught exception at line" << line << ":" << _engine.uncaughtException().toString();
-            emit errorMessage("Uncaught exception at line" + QString::number(line) + ":" + _engine.uncaughtException().toString());
+            qDebug() << "Uncaught exception at (" << _fileNameString << ") line" << line << ":" << _engine.uncaughtException().toString();
+            emit errorMessage("Uncaught exception at (" + _fileNameString + ") line" + QString::number(line) + ":" + _engine.uncaughtException().toString());
+            _engine.clearExceptions();
         }
     }
     emit scriptEnding();
@@ -665,5 +684,10 @@ void ScriptEngine::include(const QString& includeFile) {
         int line = _engine.uncaughtExceptionLineNumber();
         qDebug() << "Uncaught exception at (" << includeFile << ") line" << line << ":" << result.toString();
         emit errorMessage("Uncaught exception at (" + includeFile + ") line" + QString::number(line) + ":" + result.toString());
+        _engine.clearExceptions();
     }
 }
+
+void ScriptEngine::nodeKilled(SharedNodePointer node) {
+    _outgoingScriptAudioSequenceNumbers.remove(node->getUUID());
+}
diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h
index bf2ac40568..5b01b8124a 100644
--- a/libraries/script-engine/src/ScriptEngine.h
+++ b/libraries/script-engine/src/ScriptEngine.h
@@ -100,6 +100,8 @@ public slots:
     void include(const QString& includeFile);
     void print(const QString& message);
 
+    void nodeKilled(SharedNodePointer node);
+
 signals:
     void update(float deltaTime);
     void scriptEnding();
@@ -146,6 +148,7 @@ private:
     ScriptUUID _uuidLibrary;
     AnimationCache _animationCache;
 
+    QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
 };
 
 #endif // hifi_ScriptEngine_h
diff --git a/libraries/shared/src/CapsuleShape.cpp b/libraries/shared/src/CapsuleShape.cpp
index 5416ff92a6..12ab6ba479 100644
--- a/libraries/shared/src/CapsuleShape.cpp
+++ b/libraries/shared/src/CapsuleShape.cpp
@@ -18,9 +18,6 @@
 #include "SharedUtil.h" 
 
 
-// default axis of CapsuleShape is Y-axis
-const glm::vec3 localAxis(0.0f, 1.0f, 0.0f);
-
 CapsuleShape::CapsuleShape() : Shape(Shape::CAPSULE_SHAPE), _radius(0.0f), _halfHeight(0.0f) {}
 
 CapsuleShape::CapsuleShape(float radius, float halfHeight) : Shape(Shape::CAPSULE_SHAPE),
@@ -40,17 +37,17 @@ CapsuleShape::CapsuleShape(float radius, const glm::vec3& startPoint, const glm:
 
 /// \param[out] startPoint is the center of start cap
 void CapsuleShape::getStartPoint(glm::vec3& startPoint) const {
-    startPoint = _position - _rotation * glm::vec3(0.0f, _halfHeight, 0.0f);
+    startPoint = _translation - _rotation * glm::vec3(0.0f, _halfHeight, 0.0f);
 }
 
 /// \param[out] endPoint is the center of the end cap
 void CapsuleShape::getEndPoint(glm::vec3& endPoint) const {
-    endPoint = _position + _rotation * glm::vec3(0.0f, _halfHeight, 0.0f);
+    endPoint = _translation + _rotation * glm::vec3(0.0f, _halfHeight, 0.0f);
 }
 
 void CapsuleShape::computeNormalizedAxis(glm::vec3& axis) const {
     // default axis of a capsule is along the yAxis
-    axis = _rotation * glm::vec3(0.0f, 1.0f, 0.0f);
+    axis = _rotation * DEFAULT_CAPSULE_AXIS;
 }
 
 void CapsuleShape::setRadius(float radius) {
@@ -71,17 +68,12 @@ void CapsuleShape::setRadiusAndHalfHeight(float radius, float halfHeight) {
 
 void CapsuleShape::setEndPoints(const glm::vec3& startPoint, const glm::vec3& endPoint) {
     glm::vec3 axis = endPoint - startPoint;
-    _position = 0.5f * (endPoint + startPoint);
+    _translation = 0.5f * (endPoint + startPoint);
     float height = glm::length(axis);
     if (height > EPSILON) {
         _halfHeight = 0.5f * height;
         axis /= height;
-        glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
-        float angle = glm::angle(axis, yAxis);
-        if (angle > EPSILON) {
-            axis = glm::normalize(glm::cross(yAxis, axis));
-            _rotation = glm::angleAxis(angle, axis);
-        }
+        computeNewRotation(axis);
     }
     updateBoundingRadius();
 }
@@ -94,3 +86,13 @@ bool CapsuleShape::findRayIntersection(const glm::vec3& rayStart, const glm::vec
         // TODO: implement the raycast to return inside surface intersection for the internal rayStart.
         return findRayCapsuleIntersection(rayStart, rayDirection, capsuleStart, capsuleEnd, _radius, distance);
 }
+
+// static
+glm::quat CapsuleShape::computeNewRotation(const glm::vec3& newAxis) {
+    float angle = glm::angle(newAxis, DEFAULT_CAPSULE_AXIS);
+    if (angle > EPSILON) {
+        glm::vec3 rotationAxis = glm::normalize(glm::cross(DEFAULT_CAPSULE_AXIS, newAxis));
+        return glm::angleAxis(angle, rotationAxis);
+    }
+    return glm::quat();
+}
diff --git a/libraries/shared/src/CapsuleShape.h b/libraries/shared/src/CapsuleShape.h
index fdd6c3eda6..8d84e32a97 100644
--- a/libraries/shared/src/CapsuleShape.h
+++ b/libraries/shared/src/CapsuleShape.h
@@ -14,7 +14,11 @@
 
 #include "Shape.h"
 
+#include "SharedUtil.h"
+
 // default axis of CapsuleShape is Y-axis
+const glm::vec3 DEFAULT_CAPSULE_AXIS(0.0f, 1.0f, 0.0f);
+
 
 class CapsuleShape : public Shape {
 public:
@@ -23,26 +27,33 @@ public:
     CapsuleShape(float radius, float halfHeight, const glm::vec3& position, const glm::quat& rotation);
     CapsuleShape(float radius, const glm::vec3& startPoint, const glm::vec3& endPoint);
 
+    virtual ~CapsuleShape() {}
+
     float getRadius() const { return _radius; }
-    float getHalfHeight() const { return _halfHeight; }
+    virtual float getHalfHeight() const { return _halfHeight; }
 
     /// \param[out] startPoint is the center of start cap
-    void getStartPoint(glm::vec3& startPoint) const;
+    virtual void getStartPoint(glm::vec3& startPoint) const;
 
     /// \param[out] endPoint is the center of the end cap
-    void getEndPoint(glm::vec3& endPoint) const;
+    virtual void getEndPoint(glm::vec3& endPoint) const;
 
-    void computeNormalizedAxis(glm::vec3& axis) const;
+    virtual void computeNormalizedAxis(glm::vec3& axis) const;
 
     void setRadius(float radius);
-    void setHalfHeight(float height);
-    void setRadiusAndHalfHeight(float radius, float height);
-    void setEndPoints(const glm::vec3& startPoint, const glm::vec3& endPoint);
+    virtual void setHalfHeight(float height);
+    virtual void setRadiusAndHalfHeight(float radius, float height);
+
+    /// Sets the endpoints and updates center, rotation, and halfHeight to agree.
+    virtual void setEndPoints(const glm::vec3& startPoint, const glm::vec3& endPoint);
 
     bool findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const;
 
+    virtual float getVolume() const { return (PI * _radius * _radius) * (1.3333333333f * _radius + getHalfHeight()); }
+
 protected:
-    void updateBoundingRadius() { _boundingRadius = _radius + _halfHeight; }
+    virtual void updateBoundingRadius() { _boundingRadius = _radius + getHalfHeight(); }
+    static glm::quat computeNewRotation(const glm::vec3& newAxis);
 
     float _radius;
     float _halfHeight;
diff --git a/libraries/shared/src/CollisionInfo.cpp b/libraries/shared/src/CollisionInfo.cpp
index 38e3a4b2db..e862a22f4a 100644
--- a/libraries/shared/src/CollisionInfo.cpp
+++ b/libraries/shared/src/CollisionInfo.cpp
@@ -11,6 +11,20 @@
 
 #include "CollisionInfo.h"
 
+#include "Shape.h"
+#include "SharedUtil.h"
+
+CollisionInfo::CollisionInfo() :
+        _data(NULL),
+        _intData(0),
+        _shapeA(NULL),
+        _shapeB(NULL),
+        _damping(0.f),
+        _elasticity(1.f),
+        _contactPoint(0.f), 
+        _penetration(0.f), 
+        _addedVelocity(0.f) {
+}
 
 CollisionList::CollisionList(int maxSize) :
     _maxSize(maxSize),
@@ -18,6 +32,29 @@ CollisionList::CollisionList(int maxSize) :
     _collisions.resize(_maxSize);
 }
 
+void CollisionInfo::apply() {
+    assert(_shapeA);
+    // NOTE: Shape::computeEffectiveMass() has side effects: computes and caches partial Lagrangian coefficients
+    Shape* shapeA = const_cast<Shape*>(_shapeA);
+    float massA = shapeA->computeEffectiveMass(_penetration, _contactPoint);
+    float massB = MAX_SHAPE_MASS;
+    float totalMass = massA + massB;
+    if (_shapeB) {
+        Shape* shapeB = const_cast<Shape*>(_shapeB);
+        massB = shapeB->computeEffectiveMass(-_penetration, _contactPoint - _penetration);
+        totalMass = massA + massB;
+        if (totalMass < EPSILON) {
+            massA = massB = 1.0f;
+            totalMass = 2.0f;
+        }
+        // remember that _penetration points from A into B
+        shapeB->accumulateDelta(massA / totalMass, _penetration);
+    }   
+    // NOTE: Shape::accumulateDelta() uses the coefficients from previous call to Shape::computeEffectiveMass()
+    // remember that _penetration points from A into B
+    shapeA->accumulateDelta(massB / totalMass, -_penetration);
+}
+
 CollisionInfo* CollisionList::getNewCollision() {
     // return pointer to existing CollisionInfo, or NULL of list is full
     return (_size < _maxSize) ? &(_collisions[_size++]) : NULL;
@@ -38,17 +75,17 @@ CollisionInfo* CollisionList::getLastCollision() {
 }
 
 void CollisionList::clear() {
-    // we rely on the external context to properly set or clear the data members of a collision
-    // whenever it is used.
+    // we rely on the external context to properly set or clear the data members of CollisionInfos
     /*
     for (int i = 0; i < _size; ++i) {
         // we only clear the important stuff
         CollisionInfo& collision = _collisions[i];
-        collision._type = COLLISION_TYPE_UNKNOWN;
         //collision._data = NULL;
         //collision._intData = 0;
         //collision._floatDAta = 0.0f;
         //collision._vecData = glm::vec3(0.0f);
+        //collision._shapeA = NULL;
+        //collision._shapeB = NULL;
         //collision._damping;
         //collision._elasticity;
         //collision._contactPoint;
diff --git a/libraries/shared/src/CollisionInfo.h b/libraries/shared/src/CollisionInfo.h
index 52d5298fde..1ab06e2ef5 100644
--- a/libraries/shared/src/CollisionInfo.h
+++ b/libraries/shared/src/CollisionInfo.h
@@ -17,16 +17,7 @@
 
 #include <QVector>
 
-enum CollisionType {
-    COLLISION_TYPE_UNKNOWN = 0,
-    COLLISION_TYPE_PADDLE_HAND,
-    COLLISION_TYPE_MODEL,
-        // _data = pointer to Model that owns joint
-        // _intData = joint index
-    COLLISION_TYPE_AACUBE,
-        // _floatData = cube side
-        // _vecData = cube center
-};
+class Shape;
 
 const quint32 COLLISION_GROUP_ENVIRONMENT = 1U << 0;
 const quint32 COLLISION_GROUP_AVATARS     = 1U << 1;
@@ -41,38 +32,24 @@ const quint32 VALID_COLLISION_GROUPS = 0x0f;
 
 class CollisionInfo {
 public:
-    CollisionInfo() 
-        : _type(0),
-        _data(NULL),
-        _intData(0),
-        _damping(0.f),
-        _elasticity(1.f),
-        _contactPoint(0.f), 
-        _penetration(0.f), 
-        _addedVelocity(0.f) {
-    }
-
-    CollisionInfo(qint32 type)
-        : _type(type),
-        _data(NULL),
-        _intData(0),
-        _damping(0.f),
-        _elasticity(1.f),
-        _contactPoint(0.f), 
-        _penetration(0.f), 
-        _addedVelocity(0.f) {
-    }
-
+    CollisionInfo();
     ~CollisionInfo() {}
 
-    int _type;          // type of Collision
-
-    // the value of the *Data fields depend on the type 
+    // TODO: Andrew to get rid of these data members
     void* _data;
     int _intData;       
     float _floatData;
     glm::vec3 _vecData;
 
+    /// accumulates position changes for the shapes in this collision to resolve penetration
+    void apply();
+
+    Shape* getShapeA() const { return const_cast<Shape*>(_shapeA); }
+    Shape* getShapeB() const { return const_cast<Shape*>(_shapeB); }
+
+    const Shape* _shapeA;  // pointer to shapeA in this collision
+    const Shape* _shapeB;  // pointer to shapeB in this collision
+
     float _damping;           // range [0,1] of friction coeficient
     float _elasticity;        // range [0,1] of energy conservation
     glm::vec3 _contactPoint;  // world-frame point on BodyA that is deepest into BodyB
diff --git a/libraries/shared/src/ListShape.cpp b/libraries/shared/src/ListShape.cpp
index dcea97826e..67ec32d4b1 100644
--- a/libraries/shared/src/ListShape.cpp
+++ b/libraries/shared/src/ListShape.cpp
@@ -14,7 +14,7 @@
 // ListShapeEntry
 
 void ListShapeEntry::updateTransform(const glm::vec3& rootPosition, const glm::quat& rootRotation) {
-    _shape->setPosition(rootPosition + rootRotation * _localPosition);
+    _shape->setTranslation(rootPosition + rootRotation * _localPosition);
     _shape->setRotation(_localRotation * rootRotation);
 }
 
@@ -24,9 +24,9 @@ ListShape::~ListShape() {
     clear();
 }
 
-void ListShape::setPosition(const glm::vec3& position) {
+void ListShape::setTranslation(const glm::vec3& position) {
     _subShapeTransformsAreDirty = true;
-    Shape::setPosition(position);
+    Shape::setTranslation(position);
 }
 
 void ListShape::setRotation(const glm::quat& rotation) {
@@ -44,7 +44,7 @@ const Shape* ListShape::getSubShape(int index) const {
 void ListShape::updateSubTransforms() {
     if (_subShapeTransformsAreDirty) {
         for (int i = 0; i < _subShapeEntries.size(); ++i) {
-            _subShapeEntries[i].updateTransform(_position, _rotation);
+            _subShapeEntries[i].updateTransform(_translation, _rotation);
         }
         _subShapeTransformsAreDirty = false;
     }
diff --git a/libraries/shared/src/ListShape.h b/libraries/shared/src/ListShape.h
index 17e7d7b2b6..bd150c8246 100644
--- a/libraries/shared/src/ListShape.h
+++ b/libraries/shared/src/ListShape.h
@@ -42,7 +42,7 @@ public:
 
     ~ListShape();
 
-    void setPosition(const glm::vec3& position);
+    void setTranslation(const glm::vec3& position);
     void setRotation(const glm::quat& rotation);
 
     const Shape* getSubShape(int index) const;
diff --git a/libraries/shared/src/PhysicsEntity.cpp b/libraries/shared/src/PhysicsEntity.cpp
new file mode 100644
index 0000000000..37d1a88d67
--- /dev/null
+++ b/libraries/shared/src/PhysicsEntity.cpp
@@ -0,0 +1,211 @@
+//
+//  PhysicsEntity.cpp
+//  libraries/shared/src
+//
+//  Created by Andrew Meadows 2014.06.11
+//  Copyright 2014 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
+//
+
+#include "PhysicsEntity.h"
+
+#include "PhysicsSimulation.h"
+#include "Shape.h"
+#include "ShapeCollider.h"
+
+PhysicsEntity::PhysicsEntity() : 
+    _translation(0.0f), 
+    _rotation(), 
+    _boundingRadius(0.0f), 
+    _shapesAreDirty(true),
+    _enableShapes(false),
+    _simulation(NULL) {
+}
+
+PhysicsEntity::~PhysicsEntity() {
+    if (_simulation) {
+        _simulation->removeEntity(this);
+        _simulation = NULL;
+    }
+}
+
+void PhysicsEntity::setTranslation(const glm::vec3& translation) {
+    if (_translation != translation) {
+        _shapesAreDirty = !_shapes.isEmpty();
+        _translation = translation;
+    }
+}
+
+void PhysicsEntity::setRotation(const glm::quat& rotation) {
+    if (_rotation != rotation) {
+        _shapesAreDirty = !_shapes.isEmpty();
+        _rotation = rotation;
+    }
+}   
+
+void PhysicsEntity::setShapeBackPointers() {
+    for (int i = 0; i < _shapes.size(); i++) {
+        Shape* shape = _shapes[i];
+        if (shape) {
+            shape->setEntity(this);
+        }
+    }
+}
+
+void PhysicsEntity::setEnableShapes(bool enable) {
+    if (enable != _enableShapes) {
+        clearShapes();
+        _enableShapes = enable;
+        if (_enableShapes) {
+            buildShapes();
+        }
+    }
+}   
+
+void PhysicsEntity::clearShapes() {
+    for (int i = 0; i < _shapes.size(); ++i) {
+        delete _shapes[i];
+    }
+    _shapes.clear();
+}
+
+bool PhysicsEntity::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const {
+    int numShapes = _shapes.size();
+    float minDistance = FLT_MAX;
+    for (int j = 0; j < numShapes; ++j) {
+        const Shape* shape = _shapes[j];
+        float thisDistance = FLT_MAX;
+        if (shape && shape->findRayIntersection(origin, direction, thisDistance)) {
+            if (thisDistance < minDistance) {
+                minDistance = thisDistance;
+            }
+        }
+    }
+    if (minDistance < FLT_MAX) {
+        distance = minDistance;
+        return true;
+    }
+    return false;
+}
+
+bool PhysicsEntity::findCollisions(const QVector<const Shape*> shapes, CollisionList& collisions) {
+    bool collided = false;
+    int numTheirShapes = shapes.size();
+    for (int i = 0; i < numTheirShapes; ++i) {
+        const Shape* theirShape = shapes[i];
+        if (!theirShape) {
+            continue;
+        }
+        int numOurShapes = _shapes.size();
+        for (int j = 0; j < numOurShapes; ++j) {
+            const Shape* ourShape = _shapes.at(j);
+            if (ourShape && ShapeCollider::collideShapes(theirShape, ourShape, collisions)) {
+                collided = true;
+            }
+        }
+    }
+    return collided;
+}
+
+bool PhysicsEntity::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadius, CollisionList& collisions) {
+    bool collided = false;
+    SphereShape sphere(sphereRadius, sphereCenter);
+    for (int i = 0; i < _shapes.size(); i++) {
+        Shape* shape = _shapes[i];
+        if (!shape) {
+            continue;
+        }
+        if (ShapeCollider::collideShapes(&sphere, shape, collisions)) {
+            CollisionInfo* collision = collisions.getLastCollision();
+            collision->_data = (void*)(this);
+            collision->_intData = i;
+            collided = true;
+        }
+    }
+    return collided;
+}
+
+bool PhysicsEntity::findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions) {
+    bool collided = false;
+    PlaneShape planeShape(plane);
+    for (int i = 0; i < _shapes.size(); i++) {
+        if (_shapes.at(i) && ShapeCollider::collideShapes(&planeShape, _shapes.at(i), collisions)) {
+            CollisionInfo* collision = collisions.getLastCollision();
+            collision->_data = (void*)(this);
+            collision->_intData = i;
+            collided = true;
+        }
+    }
+    return collided;
+}
+
+// -----------------------------------------------------------
+// TODO: enforce this maximum when shapes are actually built.  The gotcha here is 
+// that the Model class (derived from PhysicsEntity) expects numShapes == numJoints, 
+// so we have to modify that code to be safe.
+const int MAX_SHAPES_PER_ENTITY = 256;
+
+// the first 256 prime numbers
+const int primes[256] = {
+      2,    3,    5,    7,   11,   13,   17,   19,   23,   29,
+     31,   37,   41,   43,   47,   53,   59,   61,   67,   71,
+     73,   79,   83,   89,   97,  101,  103,  107,  109,  113,
+    127,  131,  137,  139,  149,  151,  157,  163,  167,  173,
+    179,  181,  191,  193,  197,  199,  211,  223,  227,  229,
+    233,  239,  241,  251,  257,  263,  269,  271,  277,  281,
+    283,  293,  307,  311,  313,  317,  331,  337,  347,  349,
+    353,  359,  367,  373,  379,  383,  389,  397,  401,  409,
+    419,  421,  431,  433,  439,  443,  449,  457,  461,  463,
+    467,  479,  487,  491,  499,  503,  509,  521,  523,  541,
+    547,  557,  563,  569,  571,  577,  587,  593,  599,  601,
+    607,  613,  617,  619,  631,  641,  643,  647,  653,  659,
+    661,  673,  677,  683,  691,  701,  709,  719,  727,  733,
+    739,  743,  751,  757,  761,  769,  773,  787,  797,  809,
+    811,  821,  823,  827,  829,  839,  853,  857,  859,  863,
+    877,  881,  883,  887,  907,  911,  919,  929,  937,  941,
+    947,  953,  967,  971,  977,  983,  991,  997, 1009, 1013,
+   1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069,
+   1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151,
+   1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223,
+   1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291,
+   1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373,
+   1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451,
+   1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511,
+   1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583,
+   1597, 1601, 1607, 1609, 1613, 1619 };
+
+void PhysicsEntity::disableCollisions(int shapeIndexA, int shapeIndexB) {
+    if (shapeIndexA < MAX_SHAPES_PER_ENTITY && shapeIndexB < MAX_SHAPES_PER_ENTITY) {
+        _disabledCollisions.insert(primes[shapeIndexA] * primes[shapeIndexB]);
+    }
+}
+
+bool PhysicsEntity::collisionsAreEnabled(int shapeIndexA, int shapeIndexB) const {
+    if (shapeIndexA < MAX_SHAPES_PER_ENTITY && shapeIndexB < MAX_SHAPES_PER_ENTITY) {
+        return !_disabledCollisions.contains(primes[shapeIndexA] * primes[shapeIndexB]);
+    }
+    return false;
+}
+
+void PhysicsEntity::disableCurrentSelfCollisions() {
+    CollisionList collisions(10);
+    int numShapes = _shapes.size();
+    for (int i = 0; i < numShapes; ++i) {
+        const Shape* shape = _shapes.at(i);
+        if (!shape) {
+            continue;
+        }
+        for (int j = i+1; j < numShapes; ++j) {
+            if (!collisionsAreEnabled(i, j)) {
+                continue;
+            }
+            const Shape* otherShape = _shapes.at(j);
+            if (otherShape && ShapeCollider::collideShapes(shape, otherShape, collisions)) {
+                disableCollisions(i, j);
+                collisions.clear();
+            }
+        }
+    }
+}
diff --git a/libraries/shared/src/PhysicsEntity.h b/libraries/shared/src/PhysicsEntity.h
new file mode 100644
index 0000000000..3407ac8421
--- /dev/null
+++ b/libraries/shared/src/PhysicsEntity.h
@@ -0,0 +1,78 @@
+//
+//  PhysicsEntity.h
+//  libraries/shared/src
+//
+//  Created by Andrew Meadows 2014.05.30
+//  Copyright 2014 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
+//
+
+#ifndef hifi_PhysicsEntity_h
+#define hifi_PhysicsEntity_h
+
+#include <QVector>
+#include <QSet>
+
+#include <glm/glm.hpp>
+#include <glm/gtc/quaternion.hpp>
+
+#include "CollisionInfo.h"
+
+class Shape;
+class PhysicsSimulation;
+
+// PhysicsEntity is the base class for anything that owns one or more Shapes that collide in a 
+// PhysicsSimulation.  Each CollisionInfo generated by a PhysicsSimulation has back pointers to the 
+// two Shapes involved, and those Shapes may (optionally) have valid back pointers to their PhysicsEntity.
+
+class PhysicsEntity {
+
+public:
+    PhysicsEntity();
+    virtual ~PhysicsEntity();
+
+    void setTranslation(const glm::vec3& translation);
+    void setRotation(const glm::quat& rotation);
+
+    const glm::vec3& getTranslation() const { return _translation; }
+    const glm::quat& getRotation() const { return _rotation; }
+    float getBoundingRadius() const { return _boundingRadius; }
+
+    void setShapeBackPointers();
+
+    void setEnableShapes(bool enable);
+
+    virtual void buildShapes() = 0;
+    virtual void clearShapes();
+    const QVector<Shape*> getShapes() const { return _shapes; }
+
+    PhysicsSimulation* getSimulation() const { return _simulation; }
+
+    bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
+    bool findCollisions(const QVector<const Shape*> shapes, CollisionList& collisions);
+    bool findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadius, CollisionList& collisions);
+    bool findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions);
+
+    void disableCollisions(int shapeIndexA, int shapeIndexB);
+    bool collisionsAreEnabled(int shapeIndexA, int shapeIndexB) const;
+
+    void disableCurrentSelfCollisions();
+
+protected:
+    glm::vec3 _translation;
+    glm::quat _rotation;
+    float _boundingRadius;
+    bool _shapesAreDirty;
+    bool _enableShapes;
+    QVector<Shape*> _shapes;
+    QSet<int> _disabledCollisions;
+
+private:
+    // PhysicsSimulation is a friend so that it can set the protected _simulation backpointer
+    friend class PhysicsSimulation; 
+    PhysicsSimulation* _simulation;
+};
+
+#endif // hifi_PhysicsEntity_h
diff --git a/libraries/shared/src/PhysicsSimulation.cpp b/libraries/shared/src/PhysicsSimulation.cpp
new file mode 100644
index 0000000000..637a5e955c
--- /dev/null
+++ b/libraries/shared/src/PhysicsSimulation.cpp
@@ -0,0 +1,244 @@
+//
+//  PhysicsSimulation.cpp
+//  interface/src/avatar
+//
+//  Created by Andrew Meadows 2014.06.06
+//  Copyright 2014 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
+//
+
+#include <glm/glm.hpp>
+#include <iostream>
+
+#include "PhysicsSimulation.h"
+
+#include "PhysicsEntity.h"
+#include "Ragdoll.h"
+#include "SharedUtil.h"
+#include "ShapeCollider.h"
+
+int MAX_DOLLS_PER_SIMULATION = 16;
+int MAX_ENTITIES_PER_SIMULATION = 64;
+int MAX_COLLISIONS_PER_SIMULATION = 256;
+
+
+const int NUM_SHAPE_BITS = 6;
+const int SHAPE_INDEX_MASK = (1 << (NUM_SHAPE_BITS + 1)) - 1;
+
+PhysicsSimulation::PhysicsSimulation() : _collisionList(MAX_COLLISIONS_PER_SIMULATION), 
+        _numIterations(0), _numCollisions(0), _constraintError(0.0f), _stepTime(0) {
+}
+
+PhysicsSimulation::~PhysicsSimulation() {
+    // entities have a backpointer to this simulator that must be cleaned up
+    int numEntities = _entities.size();
+    for (int i = 0; i < numEntities; ++i) {
+        _entities[i]->_simulation = NULL;
+    }
+    _entities.clear();
+
+    // but Ragdolls do not
+    _dolls.clear();
+}
+
+bool PhysicsSimulation::addEntity(PhysicsEntity* entity) {
+    if (!entity) {
+        return false;
+    }
+    if (entity->_simulation == this) {
+        int numEntities = _entities.size();
+        for (int i = 0; i < numEntities; ++i) {
+            if (entity == _entities.at(i)) {
+                // already in list
+                assert(entity->_simulation == this);
+                return true;
+            }
+        }
+        // belongs to some other simulation
+        return false;
+    }
+    int numEntities = _entities.size();
+    if (numEntities > MAX_ENTITIES_PER_SIMULATION) {
+        // list is full
+        return false;
+    }
+    // add to list
+    entity->_simulation = this;
+    _entities.push_back(entity);
+    return true;
+}
+
+void PhysicsSimulation::removeEntity(PhysicsEntity* entity) {
+    if (!entity || !entity->_simulation || !(entity->_simulation == this)) {
+        return;
+    }
+    int numEntities = _entities.size();
+    for (int i = 0; i < numEntities; ++i) {
+        if (entity == _entities.at(i)) {
+            if (i == numEntities - 1) {
+                // remove it
+                _entities.pop_back();
+            } else {
+                // swap the last for this one
+                PhysicsEntity* lastEntity = _entities[numEntities - 1];
+                _entities.pop_back();
+                _entities[i] = lastEntity;
+            }
+            entity->_simulation = NULL;
+            break;
+        }
+    }
+}
+
+bool PhysicsSimulation::addRagdoll(Ragdoll* doll) {
+    if (!doll) {
+        return false;
+    }
+    int numDolls = _dolls.size();
+    if (numDolls > MAX_DOLLS_PER_SIMULATION) {
+        // list is full
+        return false;
+    }
+    for (int i = 0; i < numDolls; ++i) {
+        if (doll == _dolls[i]) {
+            // already in list
+            return true;
+        }
+    }
+    // add to list
+    _dolls.push_back(doll);
+    return true;
+}
+
+void PhysicsSimulation::removeRagdoll(Ragdoll* doll) {
+    int numDolls = _dolls.size();
+    for (int i = 0; i < numDolls; ++i) {
+        if (doll == _dolls[i]) {
+            if (i == numDolls - 1) {
+                // remove it
+                _dolls.pop_back();
+            } else {
+                // swap the last for this one
+                Ragdoll* lastDoll = _dolls[numDolls - 1];
+                _dolls.pop_back();
+                _dolls[i] = lastDoll;
+            }
+            break;
+        }
+    }
+}
+// TODO: Andrew to implement:
+// DONE (1) joints pull points (SpecialCapsuleShape would help solve this)
+// DONE (2) points slam shapes (SpecialCapsuleShape would help solve this)
+// DONE (3) detect collisions
+// DONE (4) collisions move points (SpecialCapsuleShape would help solve this)
+// DONE (5) enforce constraints
+// DONE (6) make sure MyAvatar creates shapes, adds to simulation with ragdoll support
+// DONE (7) support for pairwise collision bypass
+// DONE (8) process collisions
+// DONE (8a) stubbery
+// DONE (8b) shapes actually accumulate movement
+// DONE (9) verify that avatar shapes self collide
+// (10) slave rendered SkeletonModel to physical shapes
+// (10a) give SkeletonModel duplicate JointState data
+// (10b) figure out how to slave dupe JointStates to physical shapes
+// (11) add and enforce angular contraints for joints
+void PhysicsSimulation::stepForward(float deltaTime, float minError, int maxIterations, quint64 maxUsec) {
+    quint64 now = usecTimestampNow();
+    quint64 startTime = now;
+    quint64 expiry = startTime + maxUsec;
+
+    moveRagdolls(deltaTime);
+
+    int numDolls = _dolls.size();
+    _numCollisions = 0;
+    int iterations = 0;
+    float error = 0.0f;
+    do {
+        computeCollisions();
+        processCollisions();
+
+        // enforce constraints
+        error = 0.0f;
+        for (int i = 0; i < numDolls; ++i) {
+            error = glm::max(error, _dolls[i]->enforceRagdollConstraints());
+        }
+        ++iterations;
+
+        now = usecTimestampNow();
+    } while (_numCollisions != 0 && (iterations < maxIterations) && (error > minError) && (now < expiry));
+
+    _numIterations = iterations;
+    _constraintError = error;
+    _stepTime = usecTimestampNow()- startTime;
+
+#ifdef ANDREW_DEBUG
+    // temporary debug info for watching simulation performance
+    static int adebug = 0; ++adebug;
+    if (0 == (adebug % 100)) {
+        std::cout << "adebug Ni = " << _numIterations << "  E = " << error  << "  t = " << _stepTime << std::endl;  // adebug
+    }
+#endif // ANDREW_DEBUG
+}
+
+void PhysicsSimulation::moveRagdolls(float deltaTime) {
+    int numDolls = _dolls.size();
+    for (int i = 0; i < numDolls; ++i) {
+        _dolls.at(i)->stepRagdollForward(deltaTime);
+    }
+}
+
+void PhysicsSimulation::computeCollisions() {
+    _collisionList.clear();
+    // TODO: keep track of QSet<PhysicsEntity*> collidedEntities;
+    int numEntities = _entities.size();
+    for (int i = 0; i < numEntities; ++i) {
+        PhysicsEntity* entity = _entities.at(i);
+        const QVector<Shape*> shapes = entity->getShapes();
+        int numShapes = shapes.size();
+        // collide with self
+        for (int j = 0; j < numShapes; ++j) {
+            const Shape* shape = shapes.at(j);
+            if (!shape) {
+                continue;
+            }
+            for (int k = j+1; k < numShapes; ++k) {
+                const Shape* otherShape = shapes.at(k);
+                if (otherShape && entity->collisionsAreEnabled(j, k)) {
+                    ShapeCollider::collideShapes(shape, otherShape, _collisionList);
+                }
+            }
+        }
+
+        // collide with others
+        for (int j = i+1; j < numEntities; ++j) {
+            const QVector<Shape*> otherShapes = _entities.at(j)->getShapes();
+            ShapeCollider::collideShapesWithShapes(shapes, otherShapes, _collisionList);
+        }
+    }
+    _numCollisions = _collisionList.size();
+}
+
+void PhysicsSimulation::processCollisions() {
+    // walk all collisions, accumulate movement on shapes, and build a list of affected shapes
+    QSet<Shape*> shapes;
+    int numCollisions = _collisionList.size();
+    for (int i = 0; i < numCollisions; ++i) {
+        CollisionInfo* collision = _collisionList.getCollision(i);
+        collision->apply();
+        // there is always a shapeA
+        shapes.insert(collision->getShapeA());
+        // but need to check for valid shapeB
+        if (collision->_shapeB) {
+            shapes.insert(collision->getShapeB());
+        }
+    }
+    // walk all affected shapes and apply accumulated movement
+    QSet<Shape*>::const_iterator shapeItr = shapes.constBegin();
+    while (shapeItr != shapes.constEnd()) {
+        (*shapeItr)->applyAccumulatedDelta();
+        ++shapeItr;
+    }
+}
diff --git a/libraries/shared/src/PhysicsSimulation.h b/libraries/shared/src/PhysicsSimulation.h
new file mode 100644
index 0000000000..c611e06870
--- /dev/null
+++ b/libraries/shared/src/PhysicsSimulation.h
@@ -0,0 +1,60 @@
+//
+//  PhysicsSimulation.h
+//  interface/src/avatar
+//
+//  Created by Andrew Meadows 2014.06.06
+//  Copyright 2014 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
+//
+
+#ifndef hifi_PhysicsSimulation
+#define hifi_PhysicsSimulation
+
+#include <QVector>
+
+#include "CollisionInfo.h"
+
+class PhysicsEntity;
+class Ragdoll;
+
+class PhysicsSimulation {
+public:
+
+    PhysicsSimulation();
+    ~PhysicsSimulation();
+
+    /// \return true if entity was added to or is already in the list
+    bool addEntity(PhysicsEntity* entity);
+
+    void removeEntity(PhysicsEntity* entity);
+
+    /// \return true if doll was added to or is already in the list
+    bool addRagdoll(Ragdoll* doll);
+
+    void removeRagdoll(Ragdoll* doll);
+
+    /// \param minError constraint motion below this value is considered "close enough"
+    /// \param maxIterations max number of iterations before giving up
+    /// \param maxUsec max number of usec to spend enforcing constraints
+    /// \return distance of largest movement
+    void stepForward(float deltaTime, float minError, int maxIterations, quint64 maxUsec);
+
+    void moveRagdolls(float deltaTime);
+    void computeCollisions();
+    void processCollisions();
+
+private:
+    CollisionList _collisionList;
+    QVector<PhysicsEntity*> _entities;
+    QVector<Ragdoll*> _dolls;
+
+    // some stats
+    int _numIterations;
+    int _numCollisions;
+    float _constraintError;
+    quint64 _stepTime;
+};
+
+#endif // hifi_PhysicsSimulation
diff --git a/libraries/shared/src/PlaneShape.cpp b/libraries/shared/src/PlaneShape.cpp
index e9563c6d8b..15ea281510 100644
--- a/libraries/shared/src/PlaneShape.cpp
+++ b/libraries/shared/src/PlaneShape.cpp
@@ -18,7 +18,7 @@ PlaneShape::PlaneShape(const glm::vec4& coefficients) :
     Shape(Shape::PLANE_SHAPE) {
     
     glm::vec3 normal = glm::vec3(coefficients);
-    _position = -normal * coefficients.w;
+    _translation = -normal * coefficients.w;
     
     float angle = acosf(glm::dot(normal, UNROTATED_NORMAL));
     if (angle > EPSILON) {
@@ -36,7 +36,7 @@ glm::vec3 PlaneShape::getNormal() const {
 
 glm::vec4 PlaneShape::getCoefficients() const {
     glm::vec3 normal = _rotation * UNROTATED_NORMAL;
-    return glm::vec4(normal.x, normal.y, normal.z, -glm::dot(normal, _position));
+    return glm::vec4(normal.x, normal.y, normal.z, -glm::dot(normal, _translation));
 }
 
 bool PlaneShape::findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const {
@@ -44,9 +44,9 @@ bool PlaneShape::findRayIntersection(const glm::vec3& rayStart, const glm::vec3&
     float denominator = glm::dot(n, rayDirection);
     if (fabsf(denominator) < EPSILON) {
         // line is parallel to plane
-        return glm::dot(_position - rayStart, n) < EPSILON;
+        return glm::dot(_translation - rayStart, n) < EPSILON;
     } else {
-        float d = glm::dot(_position - rayStart, n) / denominator;
+        float d = glm::dot(_translation - rayStart, n) / denominator;
         if (d > 0.0f) {
             // ray points toward plane
             distance = d;
diff --git a/libraries/shared/src/Ragdoll.cpp b/libraries/shared/src/Ragdoll.cpp
new file mode 100644
index 0000000000..1d24e74864
--- /dev/null
+++ b/libraries/shared/src/Ragdoll.cpp
@@ -0,0 +1,121 @@
+//
+//  Ragdoll.cpp
+//  libraries/shared/src
+//
+//  Created by Andrew Meadows 2014.05.30
+//  Copyright 2014 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
+//
+
+#include "Ragdoll.h"
+
+#include "CapsuleShape.h"
+#include "CollisionInfo.h"
+#include "SharedUtil.h"
+#include "SphereShape.h"
+
+// ----------------------------------------------------------------------------
+// VerletPoint
+// ----------------------------------------------------------------------------
+void VerletPoint::accumulateDelta(const glm::vec3& delta) {
+    _accumulatedDelta += delta;
+    ++_numDeltas;
+}
+
+void VerletPoint::applyAccumulatedDelta() {
+    if (_numDeltas > 0) { 
+        _position += _accumulatedDelta / (float)_numDeltas;
+        _accumulatedDelta = glm::vec3(0.0f);
+        _numDeltas = 0;
+    }
+}
+
+// ----------------------------------------------------------------------------
+// FixedConstraint
+// ----------------------------------------------------------------------------
+FixedConstraint::FixedConstraint(VerletPoint* point, const glm::vec3& anchor) : _point(point), _anchor(anchor) {
+}
+
+float FixedConstraint::enforce() {
+    assert(_point != NULL);
+    // TODO: use fast approximate sqrt here
+    float distance = glm::distance(_anchor, _point->_position);
+    _point->_position = _anchor;
+    return distance;
+}
+
+void FixedConstraint::setPoint(VerletPoint* point) {
+    assert(point);
+    _point = point;
+    _point->_mass = MAX_SHAPE_MASS;
+}
+
+void FixedConstraint::setAnchor(const glm::vec3& anchor) {
+    _anchor = anchor;
+}
+
+// ----------------------------------------------------------------------------
+// DistanceConstraint
+// ----------------------------------------------------------------------------
+DistanceConstraint::DistanceConstraint(VerletPoint* startPoint, VerletPoint* endPoint) : _distance(-1.0f) {
+    _points[0] = startPoint;
+    _points[1] = endPoint;
+    _distance = glm::distance(_points[0]->_position, _points[1]->_position);
+}
+
+DistanceConstraint::DistanceConstraint(const DistanceConstraint& other) {
+    _distance = other._distance;
+    _points[0] = other._points[0];
+    _points[1] = other._points[1];
+}
+
+void DistanceConstraint::setDistance(float distance) {
+    _distance = fabsf(distance);
+}
+
+float DistanceConstraint::enforce() {
+    // TODO: use a fast distance approximation
+    float newDistance = glm::distance(_points[0]->_position, _points[1]->_position);
+    glm::vec3 direction(0.0f, 1.0f, 0.0f);
+    if (newDistance > EPSILON) {
+        direction = (_points[0]->_position - _points[1]->_position) / newDistance;
+    }
+    glm::vec3 center = 0.5f * (_points[0]->_position + _points[1]->_position);
+    _points[0]->_position = center + (0.5f * _distance) * direction;
+    _points[1]->_position = center - (0.5f * _distance) * direction;
+    return glm::abs(newDistance - _distance);
+}
+
+// ----------------------------------------------------------------------------
+// Ragdoll
+// ----------------------------------------------------------------------------
+
+Ragdoll::Ragdoll() {
+}
+
+Ragdoll::~Ragdoll() {
+    clearRagdollConstraintsAndPoints();
+}
+    
+void Ragdoll::clearRagdollConstraintsAndPoints() {
+    int numConstraints = _ragdollConstraints.size();
+    for (int i = 0; i < numConstraints; ++i) {
+        delete _ragdollConstraints[i];
+    }
+    _ragdollConstraints.clear();
+    _ragdollPoints.clear();
+}
+
+float Ragdoll::enforceRagdollConstraints() {
+    float maxDistance = 0.0f;
+    const int numConstraints = _ragdollConstraints.size();
+    for (int i = 0; i < numConstraints; ++i) {
+        DistanceConstraint* c = static_cast<DistanceConstraint*>(_ragdollConstraints[i]);
+        //maxDistance = glm::max(maxDistance, _ragdollConstraints[i]->enforce());
+        maxDistance = glm::max(maxDistance, c->enforce());
+    }
+    return maxDistance;
+}
+
diff --git a/libraries/shared/src/Ragdoll.h b/libraries/shared/src/Ragdoll.h
new file mode 100644
index 0000000000..59c1291725
--- /dev/null
+++ b/libraries/shared/src/Ragdoll.h
@@ -0,0 +1,107 @@
+//
+//  Ragdoll.h
+//  libraries/shared/src
+//
+//  Created by Andrew Meadows 2014.05.30
+//  Copyright 2014 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
+//
+
+#ifndef hifi_Ragdoll_h
+#define hifi_Ragdoll_h
+
+#include <glm/glm.hpp>
+#include <glm/gtx/quaternion.hpp>
+
+#include <QVector>
+
+class Shape;
+
+// TODO: Andrew to move VerletPoint class to its own file
+class VerletPoint {
+public:
+    VerletPoint() : _position(0.0f), _lastPosition(0.0f), _mass(1.0f), _accumulatedDelta(0.0f), _numDeltas(0) {}
+
+    void accumulateDelta(const glm::vec3& delta);
+    void applyAccumulatedDelta();
+
+    glm::vec3 getAccumulatedDelta() const { 
+        glm::vec3 foo(0.0f); 
+        if (_numDeltas > 0) { 
+            foo = _accumulatedDelta / (float)_numDeltas; 
+        } 
+        return foo; 
+    }
+
+    glm::vec3 _position;
+    glm::vec3 _lastPosition;
+    float _mass;
+
+private:
+    glm::vec3 _accumulatedDelta;
+    int _numDeltas;
+};
+
+class Constraint {
+public:
+    Constraint() {}
+    virtual ~Constraint() {}
+
+    /// Enforce contraint by moving relevant points.
+    /// \return max distance of point movement
+    virtual float enforce() = 0;
+
+protected:
+    int _type;
+};
+
+class FixedConstraint : public Constraint {
+public:
+    FixedConstraint(VerletPoint* point, const glm::vec3& anchor);
+    float enforce();
+    void setPoint(VerletPoint* point);
+    void setAnchor(const glm::vec3& anchor);
+private:
+    VerletPoint* _point;
+    glm::vec3 _anchor;
+};
+
+class DistanceConstraint : public Constraint {
+public:
+    DistanceConstraint(VerletPoint* startPoint, VerletPoint* endPoint);
+    DistanceConstraint(const DistanceConstraint& other);
+    float enforce();
+    void setDistance(float distance);
+    float getDistance() const { return _distance; }
+private:
+    float _distance;
+    VerletPoint* _points[2];
+};
+
+class Ragdoll {
+public:
+
+    Ragdoll();
+    virtual ~Ragdoll();
+
+    virtual void stepRagdollForward(float deltaTime) = 0;
+
+    /// \return max distance of point movement
+    float enforceRagdollConstraints();
+
+    // both const and non-const getPoints()
+    const QVector<VerletPoint>& getRagdollPoints() const { return _ragdollPoints; }
+    QVector<VerletPoint>& getRagdollPoints() { return _ragdollPoints; }
+
+protected:
+    void clearRagdollConstraintsAndPoints();
+    virtual void initRagdollPoints() = 0;
+    virtual void buildRagdollConstraints() = 0;
+
+    QVector<VerletPoint> _ragdollPoints;
+    QVector<Constraint*> _ragdollConstraints;
+};
+
+#endif // hifi_Ragdoll_h
diff --git a/libraries/shared/src/Shape.h b/libraries/shared/src/Shape.h
index 3926f6cd07..09ed30a116 100644
--- a/libraries/shared/src/Shape.h
+++ b/libraries/shared/src/Shape.h
@@ -15,47 +15,76 @@
 #include <glm/glm.hpp>
 #include <glm/gtc/quaternion.hpp>
 
+class PhysicsEntity;
+
+const float MAX_SHAPE_MASS = 1.0e18f; // something less than sqrt(FLT_MAX)
 
 class Shape {
 public:
+
     enum Type{
         UNKNOWN_SHAPE = 0,
         SPHERE_SHAPE,
         CAPSULE_SHAPE,
         PLANE_SHAPE,
-        BOX_SHAPE,
         LIST_SHAPE
     };
 
-    Shape() : _type(UNKNOWN_SHAPE), _boundingRadius(0.f), _position(0.f), _rotation() { }
+    Shape() : _type(UNKNOWN_SHAPE), _owningEntity(NULL), _boundingRadius(0.f), _translation(0.f), _rotation(), _mass(MAX_SHAPE_MASS) { }
     virtual ~Shape() {}
 
     int getType() const { return _type; }
-    float getBoundingRadius() const { return _boundingRadius; }
-    const glm::vec3& getPosition() const { return _position; }
-    const glm::quat& getRotation() const { return _rotation; }
 
-    virtual void setPosition(const glm::vec3& position) { _position = position; }
+    void setEntity(PhysicsEntity* entity) { _owningEntity = entity; }
+    PhysicsEntity* getEntity() const { return _owningEntity; }
+
+    float getBoundingRadius() const { return _boundingRadius; }
+
+    virtual const glm::quat& getRotation() const { return _rotation; }
     virtual void setRotation(const glm::quat& rotation) { _rotation = rotation; }
 
+    virtual void setTranslation(const glm::vec3& translation) { _translation = translation; }
+    virtual const glm::vec3& getTranslation() const { return _translation; }
+
+    virtual void setMass(float mass) { _mass = mass; }
+    virtual float getMass() const { return _mass; }
+
     virtual bool findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const = 0;
 
+    /// \param penetration of collision
+    /// \param contactPoint of collision
+    /// \return the effective mass for the collision
+    /// For most shapes has side effects: computes and caches the partial Lagrangian coefficients which will be
+    /// used in the next accumulateDelta() call.
+    virtual float computeEffectiveMass(const glm::vec3& penetration, const glm::vec3& contactPoint) { return _mass; }
+
+    /// \param relativeMassFactor the final ingredient for partial Lagrangian coefficients from computeEffectiveMass()
+    /// \param penetration the delta movement
+    virtual void accumulateDelta(float relativeMassFactor, const glm::vec3& penetration) {}
+
+    virtual void applyAccumulatedDelta() {}
+
+    /// \return volume of shape in cubic meters
+    virtual float getVolume() const { return 1.0; }
+
 protected:
     // these ctors are protected (used by derived classes only)
-    Shape(Type type) : _type(type), _boundingRadius(0.f), _position(0.f), _rotation() {}
+    Shape(Type type) : _type(type), _owningEntity(NULL), _boundingRadius(0.f), _translation(0.f), _rotation() {}
 
     Shape(Type type, const glm::vec3& position) 
-        : _type(type), _boundingRadius(0.f), _position(position), _rotation() {}
+        : _type(type), _owningEntity(NULL), _boundingRadius(0.f), _translation(position), _rotation() {}
 
     Shape(Type type, const glm::vec3& position, const glm::quat& rotation) 
-        : _type(type), _boundingRadius(0.f), _position(position), _rotation(rotation) {}
+        : _type(type), _owningEntity(NULL), _boundingRadius(0.f), _translation(position), _rotation(rotation) {}
 
     void setBoundingRadius(float radius) { _boundingRadius = radius; }
 
     int _type;
+    PhysicsEntity* _owningEntity;
     float _boundingRadius;
-    glm::vec3 _position;
+    glm::vec3 _translation;
     glm::quat _rotation;
+    float _mass;
 };
 
 #endif // hifi_Shape_h
diff --git a/libraries/shared/src/ShapeCollider.cpp b/libraries/shared/src/ShapeCollider.cpp
index bbedeb401d..ffb51660e2 100644
--- a/libraries/shared/src/ShapeCollider.cpp
+++ b/libraries/shared/src/ShapeCollider.cpp
@@ -24,7 +24,6 @@
 namespace ShapeCollider {
 
 bool collideShapes(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) {
-    // ATM we only have two shape types so we just check every case.
     // TODO: make a fast lookup for correct method
     int typeA = shapeA->getType();
     int typeB = shapeB->getType();
@@ -74,7 +73,7 @@ bool collideShapesCoarse(const QVector<const Shape*>& shapesA, const QVector<con
     tempCollisions.clear();
     foreach (const Shape* shapeA, shapesA) {
         foreach (const Shape* shapeB, shapesB) {
-            ShapeCollider::collideShapes(shapeA, shapeB, tempCollisions);
+            collideShapes(shapeA, shapeB, tempCollisions);
         }
     }
     if (tempCollisions.size() > 0) {
@@ -87,11 +86,52 @@ bool collideShapesCoarse(const QVector<const Shape*>& shapesA, const QVector<con
         }
         collision._penetration = totalPenetration;
         collision._contactPoint = averageContactPoint / (float)(tempCollisions.size());
+        // there are no valid shape pointers for this collision so we set them NULL
+        collision._shapeA = NULL;
+        collision._shapeB = NULL;
         return true;
     }
     return false;
 }
 
+bool collideShapeWithShapes(const Shape* shapeA, const QVector<Shape*>& shapes, int startIndex, CollisionList& collisions) {
+    bool collided = false;
+    if (shapeA) {
+        int numShapes = shapes.size();
+        for (int i = startIndex; i < numShapes; ++i) {
+            const Shape* shapeB = shapes.at(i);
+            if (!shapeB) {
+                continue;
+            }
+            if (collideShapes(shapeA, shapeB, collisions)) {
+                collided = true;
+                if (collisions.isFull()) {
+                    break;
+                }
+            }
+        }
+    }
+    return collided;
+}
+
+bool collideShapesWithShapes(const QVector<Shape*>& shapesA, const QVector<Shape*>& shapesB, CollisionList& collisions) {
+    bool collided = false;
+    int numShapesA = shapesA.size();
+    for (int i = 0; i < numShapesA; ++i) {
+        Shape* shapeA = shapesA.at(i);
+        if (!shapeA) {
+            continue;
+        }
+        if (collideShapeWithShapes(shapeA, shapesB, 0, collisions)) {
+            collided = true;
+            if (collisions.isFull()) {
+                break;
+            }
+        }
+    }
+    return collided;
+}
+
 bool collideShapeWithAACube(const Shape* shapeA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions) {
     int typeA = shapeA->getType();
     if (typeA == Shape::SPHERE_SHAPE) {
@@ -116,7 +156,7 @@ bool collideShapeWithAACube(const Shape* shapeA, const glm::vec3& cubeCenter, fl
 }
 
 bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB, CollisionList& collisions) {
-    glm::vec3 BA = sphereB->getPosition() - sphereA->getPosition();
+    glm::vec3 BA = sphereB->getTranslation() - sphereA->getTranslation();
     float distanceSquared = glm::dot(BA, BA);
     float totalRadius = sphereA->getRadius() + sphereB->getRadius();
     if (distanceSquared < totalRadius * totalRadius) {
@@ -132,10 +172,11 @@ bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB, Collis
         // penetration points from A into B
         CollisionInfo* collision = collisions.getNewCollision();
         if (collision) {
-            collision->_type = COLLISION_TYPE_UNKNOWN;
             collision->_penetration = BA * (totalRadius - distance);
             // contactPoint is on surface of A
-            collision->_contactPoint = sphereA->getPosition() + sphereA->getRadius() * BA;
+            collision->_contactPoint = sphereA->getTranslation() + sphereA->getRadius() * BA;
+            collision->_shapeA = sphereA;
+            collision->_shapeB = sphereB;
             return true;
         }
     }
@@ -144,7 +185,7 @@ bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB, Collis
 
 bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, CollisionList& collisions) {
     // find sphereA's closest approach to axis of capsuleB
-    glm::vec3 BA = capsuleB->getPosition() - sphereA->getPosition();
+    glm::vec3 BA = capsuleB->getTranslation() - sphereA->getTranslation();
     glm::vec3 capsuleAxis; 
     capsuleB->computeNormalizedAxis(capsuleAxis);
     float axialDistance = - glm::dot(BA, capsuleAxis);
@@ -179,8 +220,9 @@ bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, Col
             // penetration points from A into B
             collision->_penetration = (totalRadius - radialDistance) * radialAxis; // points from A into B
             // contactPoint is on surface of sphereA
-            collision->_contactPoint = sphereA->getPosition() + sphereA->getRadius() * radialAxis;
-            collision->_type = COLLISION_TYPE_UNKNOWN;
+            collision->_contactPoint = sphereA->getTranslation() + sphereA->getRadius() * radialAxis;
+            collision->_shapeA = sphereA;
+            collision->_shapeB = capsuleB;
         } else {
             // A is on B's axis, so the penetration is undefined... 
             if (absAxialDistance > capsuleB->getHalfHeight()) {
@@ -201,8 +243,9 @@ bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, Col
             float sign = (axialDistance > 0.0f) ? -1.0f : 1.0f;
             collision->_penetration = (sign * (totalRadius + capsuleB->getHalfHeight() - absAxialDistance)) * capsuleAxis;
             // contactPoint is on surface of sphereA
-            collision->_contactPoint = sphereA->getPosition() + (sign * sphereA->getRadius()) * capsuleAxis;
-            collision->_type = COLLISION_TYPE_UNKNOWN;
+            collision->_contactPoint = sphereA->getTranslation() + (sign * sphereA->getRadius()) * capsuleAxis;
+            collision->_shapeA = sphereA;
+            collision->_shapeB = capsuleB;
         }
         return true;
     }
@@ -211,14 +254,15 @@ bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, Col
 
 bool spherePlane(const SphereShape* sphereA, const PlaneShape* planeB, CollisionList& collisions) {
     glm::vec3 penetration;
-    if (findSpherePlanePenetration(sphereA->getPosition(), sphereA->getRadius(), planeB->getCoefficients(), penetration)) {
+    if (findSpherePlanePenetration(sphereA->getTranslation(), sphereA->getRadius(), planeB->getCoefficients(), penetration)) {
         CollisionInfo* collision = collisions.getNewCollision();
         if (!collision) {
             return false; // collision list is full
         }
         collision->_penetration = penetration;
-        collision->_contactPoint = sphereA->getPosition() + sphereA->getRadius() * glm::normalize(penetration);
-        collision->_type = COLLISION_TYPE_UNKNOWN;
+        collision->_contactPoint = sphereA->getTranslation() + sphereA->getRadius() * glm::normalize(penetration);
+        collision->_shapeA = sphereA;
+        collision->_shapeB = planeB;
         return true;
     }
     return false;
@@ -226,7 +270,7 @@ bool spherePlane(const SphereShape* sphereA, const PlaneShape* planeB, Collision
 
 bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, CollisionList& collisions) {
     // find sphereB's closest approach to axis of capsuleA
-    glm::vec3 AB = capsuleA->getPosition() - sphereB->getPosition();
+    glm::vec3 AB = capsuleA->getTranslation() - sphereB->getTranslation();
     glm::vec3 capsuleAxis;
     capsuleA->computeNormalizedAxis(capsuleAxis);
     float axialDistance = - glm::dot(AB, capsuleAxis);
@@ -242,14 +286,14 @@ bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, Col
         }
 
         // closestApproach = point on capsuleA's axis that is closest to sphereB's center
-        glm::vec3 closestApproach = capsuleA->getPosition() + axialDistance * capsuleAxis;
+        glm::vec3 closestApproach = capsuleA->getTranslation() + axialDistance * capsuleAxis;
 
         if (absAxialDistance > capsuleA->getHalfHeight()) {
             // sphere hits capsule on a cap 
             // --> recompute radialAxis and closestApproach
             float sign = (axialDistance > 0.0f) ? 1.0f : -1.0f;
-            closestApproach = capsuleA->getPosition() + (sign * capsuleA->getHalfHeight()) * capsuleAxis;
-            radialAxis = closestApproach - sphereB->getPosition();
+            closestApproach = capsuleA->getTranslation() + (sign * capsuleA->getHalfHeight()) * capsuleAxis;
+            radialAxis = closestApproach - sphereB->getTranslation();
             radialDistance2 = glm::length2(radialAxis);
             if (radialDistance2 > totalRadius2) {
                 return false;
@@ -268,7 +312,8 @@ bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, Col
             collision->_penetration = (radialDistance - totalRadius) * radialAxis; // points from A into B
             // contactPoint is on surface of capsuleA
             collision->_contactPoint = closestApproach - capsuleA->getRadius() * radialAxis;
-            collision->_type = COLLISION_TYPE_UNKNOWN;
+            collision->_shapeA = capsuleA;
+            collision->_shapeB = sphereB;
         } else {
             // A is on B's axis, so the penetration is undefined... 
             if (absAxialDistance > capsuleA->getHalfHeight()) {
@@ -289,7 +334,8 @@ bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, Col
                 collision->_penetration = (sign * (totalRadius + capsuleA->getHalfHeight() - absAxialDistance)) * capsuleAxis;
                 // contactPoint is on surface of sphereA
                 collision->_contactPoint = closestApproach + (sign * capsuleA->getRadius()) * capsuleAxis;
-                collision->_type = COLLISION_TYPE_UNKNOWN;
+                collision->_shapeA = capsuleA;
+                collision->_shapeB = sphereB;
             }
         }
         return true;
@@ -302,8 +348,8 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB,
     capsuleA->computeNormalizedAxis(axisA);
     glm::vec3 axisB;
     capsuleB->computeNormalizedAxis(axisB);
-    glm::vec3 centerA = capsuleA->getPosition();
-    glm::vec3 centerB = capsuleB->getPosition();
+    glm::vec3 centerA = capsuleA->getTranslation();
+    glm::vec3 centerB = capsuleB->getTranslation();
 
     // NOTE: The formula for closest approach between two lines is:
     // d = [(B - A) . (a - (a.b)b)] / (1 - (a.b)^2)
@@ -361,7 +407,8 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB,
             collision->_penetration = BA * (totalRadius - distance);
             // contactPoint is on surface of A
             collision->_contactPoint = centerA + distanceA * axisA + capsuleA->getRadius() * BA;
-            collision->_type = COLLISION_TYPE_UNKNOWN;
+            collision->_shapeA = capsuleA;
+            collision->_shapeB = capsuleB;
             return true;
         }
     } else {
@@ -427,7 +474,8 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB,
             // average the internal pair, and then do the math from centerB
             collision->_contactPoint = centerB + (0.5f * (points[1] + points[2])) * axisB 
                 + (capsuleA->getRadius() - distance) * BA;
-            collision->_type = COLLISION_TYPE_UNKNOWN;
+            collision->_shapeA = capsuleA;
+            collision->_shapeB = capsuleB;
             return true;
         }
     }
@@ -447,7 +495,8 @@ bool capsulePlane(const CapsuleShape* capsuleA, const PlaneShape* planeB, Collis
         collision->_penetration = penetration;
         glm::vec3 deepestEnd = (glm::dot(start, glm::vec3(plane)) < glm::dot(end, glm::vec3(plane))) ? start : end;
         collision->_contactPoint = deepestEnd + capsuleA->getRadius() * glm::normalize(penetration);
-        collision->_type = COLLISION_TYPE_UNKNOWN;
+        collision->_shapeA = capsuleA;
+        collision->_shapeB = planeB;
         return true;
     }
     return false;
@@ -455,15 +504,16 @@ bool capsulePlane(const CapsuleShape* capsuleA, const PlaneShape* planeB, Collis
 
 bool planeSphere(const PlaneShape* planeA, const SphereShape* sphereB, CollisionList& collisions) {
     glm::vec3 penetration;
-    if (findSpherePlanePenetration(sphereB->getPosition(), sphereB->getRadius(), planeA->getCoefficients(), penetration)) {
+    if (findSpherePlanePenetration(sphereB->getTranslation(), sphereB->getRadius(), planeA->getCoefficients(), penetration)) {
         CollisionInfo* collision = collisions.getNewCollision();
         if (!collision) {
             return false; // collision list is full
         }
         collision->_penetration = -penetration;
-        collision->_contactPoint = sphereB->getPosition() +
+        collision->_contactPoint = sphereB->getTranslation() +
             (sphereB->getRadius() / glm::length(penetration) - 1.0f) * penetration;
-        collision->_type = COLLISION_TYPE_UNKNOWN;
+        collision->_shapeA = planeA;
+        collision->_shapeB = sphereB;
         return true;
     }
     return false;
@@ -482,7 +532,8 @@ bool planeCapsule(const PlaneShape* planeA, const CapsuleShape* capsuleB, Collis
         collision->_penetration = -penetration;
         glm::vec3 deepestEnd = (glm::dot(start, glm::vec3(plane)) < glm::dot(end, glm::vec3(plane))) ? start : end;
         collision->_contactPoint = deepestEnd + (capsuleB->getRadius() / glm::length(penetration) - 1.0f) * penetration;
-        collision->_type = COLLISION_TYPE_UNKNOWN;
+        collision->_shapeA = planeA;
+        collision->_shapeB = capsuleB;
         return true;
     }
     return false;
@@ -668,15 +719,15 @@ bool sphereAACube(const glm::vec3& sphereCenter, float sphereRadius, const glm::
             direction /= lengthDirection;
 
             // compute collision details
-            collision->_type = COLLISION_TYPE_AACUBE;
             collision->_floatData = cubeSide;
             collision->_vecData = cubeCenter;
             collision->_penetration = (halfCubeSide * lengthDirection + sphereRadius - maxBA * glm::dot(BA, direction)) * direction;
             collision->_contactPoint = sphereCenter + sphereRadius * direction;
         }
-        collision->_type = COLLISION_TYPE_AACUBE;
         collision->_floatData = cubeSide;
         collision->_vecData = cubeCenter;
+        collision->_shapeA = NULL;
+        collision->_shapeB = NULL;
         return true;
     } else if (sphereRadius + halfCubeSide > distance) {
         // NOTE: for cocentric approximation we collide sphere and cube as two spheres which means 
@@ -688,9 +739,10 @@ bool sphereAACube(const glm::vec3& sphereCenter, float sphereRadius, const glm::
             // contactPoint is on surface of A
             collision->_contactPoint = sphereCenter + collision->_penetration;
 
-            collision->_type = COLLISION_TYPE_AACUBE;
             collision->_floatData = cubeSide;
             collision->_vecData = cubeCenter;
+            collision->_shapeA = NULL;
+            collision->_shapeB = NULL;
             return true;
         }
     }
@@ -726,6 +778,8 @@ bool sphereAACube_StarkAngles(const glm::vec3& sphereCenter, float sphereRadius,
                 collision->_penetration = glm::dot(surfaceAB, direction) * direction;
                 // contactPoint is on surface of A
                 collision->_contactPoint = sphereCenter + sphereRadius * direction;
+                collision->_shapeA = NULL;
+                collision->_shapeB = NULL;
                 return true;
             }
         }
@@ -738,6 +792,8 @@ bool sphereAACube_StarkAngles(const glm::vec3& sphereCenter, float sphereRadius,
             collision->_penetration = (sphereRadius + 0.5f * cubeSide) * glm::vec3(0.0f, -1.0f, 0.0f);
             // contactPoint is on surface of A
             collision->_contactPoint = sphereCenter + collision->_penetration;
+            collision->_shapeA = NULL;
+            collision->_shapeB = NULL;
             return true;
         }
     }
@@ -746,21 +802,21 @@ bool sphereAACube_StarkAngles(const glm::vec3& sphereCenter, float sphereRadius,
 */
 
 bool sphereAACube(const SphereShape* sphereA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions) {
-    return sphereAACube(sphereA->getPosition(), sphereA->getRadius(), cubeCenter, cubeSide, collisions);
+    return sphereAACube(sphereA->getTranslation(), sphereA->getRadius(), cubeCenter, cubeSide, collisions);
 }
 
 bool capsuleAACube(const CapsuleShape* capsuleA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions) {
     // find nerest approach of capsule line segment to cube
     glm::vec3 capsuleAxis;
     capsuleA->computeNormalizedAxis(capsuleAxis);
-    float offset = glm::dot(cubeCenter - capsuleA->getPosition(), capsuleAxis);
+    float offset = glm::dot(cubeCenter - capsuleA->getTranslation(), capsuleAxis);
     float halfHeight = capsuleA->getHalfHeight();
     if (offset > halfHeight) {
         offset = halfHeight;
     } else if (offset < -halfHeight) {
         offset = -halfHeight;
     }
-    glm::vec3 nearestApproach = capsuleA->getPosition() + offset * capsuleAxis;
+    glm::vec3 nearestApproach = capsuleA->getTranslation() + offset * capsuleAxis;
     // collide nearest approach like a sphere at that point
     return sphereAACube(nearestApproach, capsuleA->getRadius(), cubeCenter, cubeSide, collisions);
 }
diff --git a/libraries/shared/src/ShapeCollider.h b/libraries/shared/src/ShapeCollider.h
index 8261aceaf3..b1be75fa40 100644
--- a/libraries/shared/src/ShapeCollider.h
+++ b/libraries/shared/src/ShapeCollider.h
@@ -12,6 +12,8 @@
 #ifndef hifi_ShapeCollider_h
 #define hifi_ShapeCollider_h
 
+#include <QVector>
+
 #include "CapsuleShape.h"
 #include "CollisionInfo.h"
 #include "ListShape.h"
@@ -33,6 +35,9 @@ namespace ShapeCollider {
     /// \return true if any shapes collide
     bool collideShapesCoarse(const QVector<const Shape*>& shapesA, const QVector<const Shape*>& shapesB, CollisionInfo& collision);
 
+    bool collideShapeWithShapes(const Shape* shapeA, const QVector<Shape*>& shapes, int startIndex, CollisionList& collisions);
+    bool collideShapesWithShapes(const QVector<Shape*>& shapesA, const QVector<Shape*>& shapesB, CollisionList& collisions);
+
     /// \param shapeA a pointer to a shape (cannot be NULL)
     /// \param cubeCenter center of cube
     /// \param cubeSide lenght of side of cube
diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h
index dbbfb02365..e5c2a0afc9 100644
--- a/libraries/shared/src/SharedUtil.h
+++ b/libraries/shared/src/SharedUtil.h
@@ -68,7 +68,7 @@ float randFloat();
 int randIntInRange (int min, int max);
 float randFloatInRange (float min,float max);
 float randomSign(); /// \return -1.0 or 1.0
-unsigned char randomColorValue(int minimum);
+unsigned char randomColorValue(int minimum = 0);
 bool randomBoolean();
 
 glm::quat safeMix(const glm::quat& q1, const glm::quat& q2, float alpha);
diff --git a/libraries/shared/src/SphereShape.cpp b/libraries/shared/src/SphereShape.cpp
index 49137fac43..c77b0c97fb 100644
--- a/libraries/shared/src/SphereShape.cpp
+++ b/libraries/shared/src/SphereShape.cpp
@@ -17,14 +17,14 @@ bool SphereShape::findRayIntersection(const glm::vec3& rayStart, const glm::vec3
     float r2 = _boundingRadius * _boundingRadius;
 
     // compute closest approach (CA)
-    float a = glm::dot(_position - rayStart, rayDirection); // a = distance from ray-start to CA
-    float b2 = glm::distance2(_position, rayStart + a * rayDirection); // b2 = squared distance from sphere-center to CA
+    float a = glm::dot(_translation - rayStart, rayDirection); // a = distance from ray-start to CA
+    float b2 = glm::distance2(_translation, rayStart + a * rayDirection); // b2 = squared distance from sphere-center to CA
     if (b2 > r2) {
         // ray does not hit sphere
         return false;
     }
     float c = sqrtf(r2 - b2); // c = distance from CA to sphere surface along rayDirection
-    float d2 = glm::distance2(rayStart, _position); // d2 = squared distance from sphere-center to ray-start
+    float d2 = glm::distance2(rayStart, _translation); // d2 = squared distance from sphere-center to ray-start
     if (a < 0.0f) {
         // ray points away from sphere-center
         if (d2 > r2) {
diff --git a/libraries/shared/src/SphereShape.h b/libraries/shared/src/SphereShape.h
index e87b8acab1..d2f2a8596f 100644
--- a/libraries/shared/src/SphereShape.h
+++ b/libraries/shared/src/SphereShape.h
@@ -14,6 +14,8 @@
 
 #include "Shape.h"
 
+#include "SharedUtil.h"
+
 class SphereShape : public Shape {
 public:
     SphereShape() : Shape(Shape::SPHERE_SHAPE) {}
@@ -26,11 +28,15 @@ public:
         _boundingRadius = radius;
     }
 
+    virtual ~SphereShape() {}
+
     float getRadius() const { return _boundingRadius; }
 
     void setRadius(float radius) { _boundingRadius = radius; }
 
     bool findRayIntersection(const glm::vec3& rayStart, const glm::vec3& rayDirection, float& distance) const;
+
+    float getVolume() const { return 1.3333333333f * PI * _boundingRadius * _boundingRadius * _boundingRadius; }
 };
 
 #endif // hifi_SphereShape_h
diff --git a/libraries/shared/src/VerletCapsuleShape.cpp b/libraries/shared/src/VerletCapsuleShape.cpp
new file mode 100644
index 0000000000..3ac4899682
--- /dev/null
+++ b/libraries/shared/src/VerletCapsuleShape.cpp
@@ -0,0 +1,166 @@
+//
+//  VerletCapsuleShape.cpp
+//  libraries/shared/src
+//
+//  Created by Andrew Meadows on 2014.06.16
+//  Copyright 2014 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
+//
+
+#include "VerletCapsuleShape.h"
+
+#include "Ragdoll.h"    // for VerletPoint
+#include "SharedUtil.h"
+
+VerletCapsuleShape::VerletCapsuleShape(VerletPoint* startPoint, VerletPoint* endPoint) : 
+        CapsuleShape(), _startPoint(startPoint), _endPoint(endPoint), _startLagrangeCoef(0.5f), _endLagrangeCoef(0.5f) {
+    assert(startPoint);
+    assert(endPoint);
+    _halfHeight = 0.5f * glm::distance(_startPoint->_position, _endPoint->_position);
+    updateBoundingRadius();
+}
+
+VerletCapsuleShape::VerletCapsuleShape(float radius, VerletPoint* startPoint, VerletPoint* endPoint) :
+        CapsuleShape(radius, 1.0f), _startPoint(startPoint), _endPoint(endPoint), 
+        _startLagrangeCoef(0.5f), _endLagrangeCoef(0.5f) {
+    assert(startPoint);
+    assert(endPoint);
+    _halfHeight = 0.5f * glm::distance(_startPoint->_position, _endPoint->_position);
+    updateBoundingRadius();
+}
+
+const glm::quat& VerletCapsuleShape::getRotation() const {
+    // NOTE: The "rotation" of this shape must be computed on the fly, 
+    // which makes this method MUCH more more expensive than you might expect.
+    glm::vec3 axis;
+    computeNormalizedAxis(axis);
+    VerletCapsuleShape* thisCapsule = const_cast<VerletCapsuleShape*>(this);
+    thisCapsule->_rotation = computeNewRotation(axis);
+    return _rotation;
+}
+
+void VerletCapsuleShape::setRotation(const glm::quat& rotation) {
+    // NOTE: this method will update the verlet points, which is probably not 
+    // what you want to do.  Only call this method if you know what you're doing.
+
+    // update points such that they have the same center but a different axis
+    glm::vec3 center = getTranslation();
+    float halfHeight = getHalfHeight();
+    glm::vec3 axis = rotation * DEFAULT_CAPSULE_AXIS;
+    _startPoint->_position = center - halfHeight * axis;
+    _endPoint->_position = center + halfHeight * axis;
+}
+
+void VerletCapsuleShape::setTranslation(const glm::vec3& position) {
+    // NOTE: this method will update the verlet points, which is probably not 
+    // what you want to do.  Only call this method if you know what you're doing.
+
+    // update the points such that their center is at position
+    glm::vec3 movement = position - getTranslation();
+    _startPoint->_position += movement;
+    _endPoint->_position += movement;
+}
+
+const glm::vec3& VerletCapsuleShape::getTranslation() const {
+    // the "translation" of this shape must be computed on the fly
+    VerletCapsuleShape* thisCapsule = const_cast<VerletCapsuleShape*>(this);
+    thisCapsule->_translation = 0.5f * (_startPoint->_position + _endPoint->_position);
+    return _translation;
+}
+
+float VerletCapsuleShape::computeEffectiveMass(const glm::vec3& penetration, const glm::vec3& contactPoint) {
+    glm::vec3 startLeg = _startPoint->_position - contactPoint;
+    glm::vec3 endLeg = _endPoint->_position - contactPoint;
+
+    // TODO: use fast approximate distance calculations here
+    float startLength = glm::length(startLeg);
+    float endlength = glm::length(endLeg);
+
+    // The raw coefficient is proportional to the other leg's length multiplied by the dot-product
+    // of the penetration and this leg direction.  We don't worry about the common penetration length 
+    // because it is normalized out later.
+    float startCoef = glm::abs(glm::dot(startLeg, penetration)) * endlength / (startLength + EPSILON);
+    float endCoef = glm::abs(glm::dot(endLeg, penetration)) * startLength / (endlength + EPSILON);
+
+    float maxCoef = glm::max(startCoef, endCoef);
+    if (maxCoef > EPSILON) {
+        // One of these coeficients will be 1.0, the other will be less --> 
+        // one endpoint will move the full amount while the other will move less.
+        _startLagrangeCoef = startCoef / maxCoef;
+        _endLagrangeCoef = endCoef / maxCoef;
+        assert(!glm::isnan(_startLagrangeCoef));
+        assert(!glm::isnan(_startLagrangeCoef));
+    } else {
+        // The coefficients are the same --> the collision will move both equally
+        // as if the object were solid.
+        _startLagrangeCoef = 1.0f;
+        _endLagrangeCoef = 1.0f;
+    }
+    // the effective mass is the weighted sum of the two endpoints
+    return _startLagrangeCoef * _startPoint->_mass + _endLagrangeCoef * _endPoint->_mass;
+}
+
+void VerletCapsuleShape::accumulateDelta(float relativeMassFactor, const glm::vec3& penetration) {
+    assert(!glm::isnan(relativeMassFactor));
+    _startPoint->accumulateDelta(relativeMassFactor * _startLagrangeCoef * penetration);
+    _endPoint->accumulateDelta(relativeMassFactor * _endLagrangeCoef * penetration);
+}
+
+void VerletCapsuleShape::applyAccumulatedDelta() {
+    _startPoint->applyAccumulatedDelta();
+    _endPoint->applyAccumulatedDelta();
+}
+
+// virtual
+float VerletCapsuleShape::getHalfHeight() const {
+    return 0.5f * glm::distance(_startPoint->_position, _endPoint->_position);
+}
+
+// virtual
+void VerletCapsuleShape::getStartPoint(glm::vec3& startPoint) const {
+    startPoint = _startPoint->_position;
+}
+
+// virtual
+void VerletCapsuleShape::getEndPoint(glm::vec3& endPoint) const {
+    endPoint = _endPoint->_position;
+}
+
+// virtual
+void VerletCapsuleShape::computeNormalizedAxis(glm::vec3& axis) const {
+    glm::vec3 unormalizedAxis = _endPoint->_position - _startPoint->_position;
+    float fullLength = glm::length(unormalizedAxis);
+    if (fullLength > EPSILON) {
+        axis = unormalizedAxis / fullLength;
+    } else {
+        // the axis is meaningless, but we fill it with a normalized direction
+        // just in case the calling context assumes it really is normalized.
+        axis = glm::vec3(0.0f, 1.0f, 0.0f);
+    }
+}
+
+// virtual 
+void VerletCapsuleShape::setHalfHeight(float halfHeight) {
+    // push points along axis so they are 2*halfHeight apart
+    glm::vec3 center = getTranslation();
+    glm::vec3 axis;
+    computeNormalizedAxis(axis);
+    _startPoint->_position = center - halfHeight * axis;
+    _endPoint->_position = center + halfHeight * axis;
+    _boundingRadius = _radius + halfHeight;
+}
+
+// virtual 
+void VerletCapsuleShape::setRadiusAndHalfHeight(float radius, float halfHeight) {
+    _radius = radius;
+    setHalfHeight(halfHeight);
+}
+
+// virtual
+void VerletCapsuleShape::setEndPoints(const glm::vec3& startPoint, const glm::vec3& endPoint) {
+    _startPoint->_position = startPoint;
+    _endPoint->_position = endPoint;
+    updateBoundingRadius();
+}
diff --git a/libraries/shared/src/VerletCapsuleShape.h b/libraries/shared/src/VerletCapsuleShape.h
new file mode 100644
index 0000000000..1fd84f5b1e
--- /dev/null
+++ b/libraries/shared/src/VerletCapsuleShape.h
@@ -0,0 +1,83 @@
+//
+//  VerletCapsuleShape.h
+//  libraries/shared/src
+//
+//  Created by Andrew Meadows on 2014.06.16
+//  Copyright 2014 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
+//
+
+#ifndef hifi_VerletCapsuleShape_h
+#define hifi_VerletCapsuleShape_h
+
+#include "CapsuleShape.h"
+
+
+// The VerletCapsuleShape is similar to a regular CapsuleShape, except it keeps a pointer
+// to its endpoints which are owned by some other data structure (a verlet simulation system).  
+// This makes it easier for the points to be moved around by constraints in the system
+// as well as collisions with the shape, however it has some drawbacks:
+//
+// (1) The Shape::_translation and ::_rotation data members are not used (wasted)
+//
+// (2) A VerletShape doesn't own the points that it uses, so you must be careful not to
+//     leave dangling pointers around.
+//
+// (3) Some const methods of VerletCapsuleShape are much more expensive than you might think.
+//     For example getHalfHeight() and setHalfHeight() methods must do extra computation.  In
+//     particular setRotation() is significantly more expensive than for the CapsuleShape.
+//     Not too expensive to use when setting up shapes, but you woudln't want to use it deep
+//     down in a hot simulation loop, such as when processing collision results.  Best to
+//     just let the verlet simulation do its thing and not try to constantly force a rotation.
+
+class VerletPoint;
+
+class VerletCapsuleShape : public CapsuleShape {
+public:
+    VerletCapsuleShape(VerletPoint* startPoint, VerletPoint* endPoint);
+    VerletCapsuleShape(float radius, VerletPoint* startPoint, VerletPoint* endPoint);
+
+    // virtual overrides from Shape
+    const glm::quat& getRotation() const;
+    void setRotation(const glm::quat& rotation);
+    void setTranslation(const glm::vec3& position);
+    const glm::vec3& getTranslation() const;
+    float computeEffectiveMass(const glm::vec3& penetration, const glm::vec3& contactPoint);
+    void accumulateDelta(float relativeMassFactor, const glm::vec3& penetration);
+    void applyAccumulatedDelta();
+
+    //float getRadius() const { return _radius; }
+    virtual float getHalfHeight() const;
+
+    /// \param[out] startPoint is the center of start cap
+    void getStartPoint(glm::vec3& startPoint) const;
+
+    /// \param[out] endPoint is the center of the end cap
+    void getEndPoint(glm::vec3& endPoint) const;
+
+    /// \param[out] axis is a normalized vector that points from start to end
+    void computeNormalizedAxis(glm::vec3& axis) const;
+
+    //void setRadius(float radius);
+    void setHalfHeight(float halfHeight);
+    void setRadiusAndHalfHeight(float radius, float halfHeight);
+    void setEndPoints(const glm::vec3& startPoint, const glm::vec3& endPoint);
+
+    //void assignEndPoints(glm::vec3* startPoint, glm::vec3* endPoint);
+
+protected:
+    // NOTE: VerletCapsuleShape does NOT own the data in its points.
+    VerletPoint* _startPoint;
+    VerletPoint* _endPoint;
+
+    // The LagrangeCoef's are numerical weights for distributing collision movement
+    // between the relevant VerletPoints associated with this shape.  They are functions
+    // of the movement parameters and are computed (and cached) in computeEffectiveMass() 
+    // and then used in the subsequent accumulateDelta().
+    float _startLagrangeCoef;
+    float _endLagrangeCoef;
+};
+
+#endif // hifi_VerletCapsuleShape_h
diff --git a/libraries/shared/src/VerletSphereShape.cpp b/libraries/shared/src/VerletSphereShape.cpp
new file mode 100644
index 0000000000..10c40c6611
--- /dev/null
+++ b/libraries/shared/src/VerletSphereShape.cpp
@@ -0,0 +1,50 @@
+//
+//  VerletSphereShape.cpp
+//  libraries/shared/src
+//
+//  Created by Andrew Meadows on 2014.06.16
+//  Copyright 2014 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
+//
+
+#include "VerletSphereShape.h"
+
+#include "Ragdoll.h"    // for VerletPoint
+
+VerletSphereShape::VerletSphereShape(VerletPoint* centerPoint) : SphereShape() { 
+    assert(centerPoint);
+    _point = centerPoint;
+}
+
+VerletSphereShape::VerletSphereShape(float radius, VerletPoint* centerPoint) : SphereShape(radius) {
+    assert(centerPoint);
+    _point = centerPoint;
+}
+
+// virtual from Shape class
+void VerletSphereShape::setTranslation(const glm::vec3& position) {
+    _point->_position = position;
+    _point->_lastPosition = position;
+}
+
+// virtual from Shape class
+const glm::vec3& VerletSphereShape::getTranslation() const {
+    return _point->_position;
+}
+
+// virtual
+float VerletSphereShape::computeEffectiveMass(const glm::vec3& penetration, const glm::vec3& contactPoint) {
+    return _point->_mass;
+}
+
+// virtual
+void VerletSphereShape::accumulateDelta(float relativeMassFactor, const glm::vec3& penetration) {
+    _point->accumulateDelta(relativeMassFactor * penetration);
+}
+
+// virtual
+void VerletSphereShape::applyAccumulatedDelta() {
+    _point->applyAccumulatedDelta();
+}
diff --git a/libraries/shared/src/VerletSphereShape.h b/libraries/shared/src/VerletSphereShape.h
new file mode 100644
index 0000000000..65da3b2597
--- /dev/null
+++ b/libraries/shared/src/VerletSphereShape.h
@@ -0,0 +1,47 @@
+//
+//  VerletSphereShape.h
+//  libraries/shared/src
+//
+//  Created by Andrew Meadows on 2014.06.16
+//  Copyright 2014 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
+//
+
+#ifndef hifi_VerletSphereShape_h
+#define hifi_VerletSphereShape_h
+
+#include "SphereShape.h"
+
+// The VerletSphereShape is similar to a regular SphereShape, except it keeps a pointer
+// to its center which is owned by some other data structure (a verlet simulation system).  
+// This makes it easier for the points to be moved around by constraints in the system
+// as well as collisions with the shape, however it has some drawbacks:
+//
+// (1) The Shape::_translation data member is not used (wasted)
+//
+// (2) A VerletShape doesn't own the points that it uses, so you must be careful not to
+//     leave dangling pointers around.
+
+class VerletPoint;
+
+class VerletSphereShape : public SphereShape {
+public:
+    VerletSphereShape(VerletPoint* point);
+
+    VerletSphereShape(float radius, VerletPoint* centerPoint);
+
+    // virtual overrides from Shape
+    void setTranslation(const glm::vec3& position);
+    const glm::vec3& getTranslation() const;
+    float computeEffectiveMass(const glm::vec3& penetration, const glm::vec3& contactPoint);
+    void accumulateDelta(float relativeMassFactor, const glm::vec3& penetration);
+    void applyAccumulatedDelta();
+
+protected:
+    // NOTE: VerletSphereShape does NOT own its _point
+    VerletPoint* _point;
+};
+
+#endif // hifi_VerletSphereShape_h
diff --git a/tests/audio/CMakeLists.txt b/tests/audio/CMakeLists.txt
new file mode 100644
index 0000000000..5c5178cb71
--- /dev/null
+++ b/tests/audio/CMakeLists.txt
@@ -0,0 +1,40 @@
+cmake_minimum_required(VERSION 2.8)
+
+if (WIN32)
+  cmake_policy (SET CMP0020 NEW)
+endif (WIN32)
+
+set(TARGET_NAME audio-tests)
+
+set(ROOT_DIR ../..)
+set(MACRO_DIR ${ROOT_DIR}/cmake/macros)
+
+# setup for find modules
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/")
+
+#find_package(Qt5Network REQUIRED)
+#find_package(Qt5Script REQUIRED)
+#find_package(Qt5Widgets REQUIRED)
+
+include(${MACRO_DIR}/SetupHifiProject.cmake)
+setup_hifi_project(${TARGET_NAME} TRUE)
+
+include(${MACRO_DIR}/AutoMTC.cmake)
+auto_mtc(${TARGET_NAME} ${ROOT_DIR})
+
+#qt5_use_modules(${TARGET_NAME} Network Script Widgets)
+
+#include glm
+include(${MACRO_DIR}/IncludeGLM.cmake)
+include_glm(${TARGET_NAME} ${ROOT_DIR})
+
+# link in the shared libraries
+include(${MACRO_DIR}/LinkHifiLibrary.cmake)
+link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR})
+link_hifi_library(audio ${TARGET_NAME} ${ROOT_DIR})
+link_hifi_library(networking ${TARGET_NAME} ${ROOT_DIR})
+
+IF (WIN32)
+    target_link_libraries(${TARGET_NAME} Winmm Ws2_32)
+ENDIF(WIN32)
+
diff --git a/tests/audio/src/AudioRingBufferTests.cpp b/tests/audio/src/AudioRingBufferTests.cpp
new file mode 100644
index 0000000000..506e81e13e
--- /dev/null
+++ b/tests/audio/src/AudioRingBufferTests.cpp
@@ -0,0 +1,146 @@
+//
+//  AudioRingBufferTests.cpp
+//  tests/audio/src
+//
+//  Created by Yixin Wang on 6/24/2014
+//  Copyright 2014 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
+//
+
+#include "AudioRingBufferTests.h"
+
+#include "SharedUtil.h"
+
+void AudioRingBufferTests::assertBufferSize(const AudioRingBuffer& buffer, int samples) {
+    if (buffer.samplesAvailable() != samples) {
+        qDebug("Unexpected num samples available! Exptected: %d  Actual: %d\n", samples, buffer.samplesAvailable());
+    }
+}
+
+void AudioRingBufferTests::runAllTests() {
+
+    int16_t writeData[10000];
+    for (int i = 0; i < 10000; i++) { writeData[i] = i; }
+    int writeIndexAt;
+
+    int16_t readData[10000];
+    int readIndexAt;
+    
+
+    AudioRingBuffer ringBuffer(10); // makes buffer of 100 int16_t samples
+    for (int T = 0; T < 300; T++) {
+        
+        writeIndexAt = 0;
+        readIndexAt = 0;
+
+        // write 73 samples, 73 samples in buffer
+        writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 73) / sizeof(int16_t);
+        assertBufferSize(ringBuffer, 73);
+
+        // read 43 samples, 30 samples in buffer
+        readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 43) / sizeof(int16_t);
+        assertBufferSize(ringBuffer, 30);
+
+        // write 70 samples, 100 samples in buffer (full)
+        writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 70) / sizeof(int16_t);
+        assertBufferSize(ringBuffer, 100);
+
+        // read 100 samples, 0 samples in buffer (empty)
+        readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 100) / sizeof(int16_t);
+        assertBufferSize(ringBuffer, 0);
+
+
+        // verify 143 samples of read data
+        for (int i = 0; i < 143; i++) {
+            if (readData[i] != i) {
+                qDebug("first readData[%d] incorrect!  Expcted: %d  Actual: %d", i, i, readData[i]);
+                return;
+            }
+        }
+
+
+        writeIndexAt = 0;
+        readIndexAt = 0;
+
+        // write 59 samples, 59 samples in buffer
+        writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 59) / sizeof(int16_t);
+        assertBufferSize(ringBuffer, 59);
+
+        // write 99 samples, 100 samples in buffer
+        writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 99) / sizeof(int16_t);
+        assertBufferSize(ringBuffer, 100);
+
+        // read 100 samples, 0 samples in buffer
+        readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 100) / sizeof(int16_t);
+        assertBufferSize(ringBuffer, 0);
+
+        // verify 100 samples of read data
+        for (int i = 0; i < 100; i++) {
+            readData[i] = writeIndexAt - 100 + i;
+        }
+
+
+
+
+        writeIndexAt = 0;
+        readIndexAt = 0;
+
+        // write 77 samples, 77 samples in buffer
+        writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 77) / sizeof(int16_t);
+        assertBufferSize(ringBuffer, 77);
+
+        // write 24 samples, 100 samples in buffer (overwrote one sample: "0")
+        writeIndexAt += ringBuffer.writeSamples(&writeData[writeIndexAt], 24) / sizeof(int16_t);
+        assertBufferSize(ringBuffer, 100);
+
+        // write 29 silent samples, 100 samples in buffer, make sure non were added
+        int samplesWritten;
+        if ((samplesWritten = ringBuffer.addSilentFrame(29)) != 0) {
+            qDebug("addSilentFrame(29) incorrect!  Expected: 0  Actual: %d", samplesWritten);
+            return;
+        }
+        assertBufferSize(ringBuffer, 100);
+
+        // read 3 samples, 97 samples in buffer (expect to read "1", "2", "3")
+        readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 3) / sizeof(int16_t);
+        for (int i = 0; i < 3; i++) {
+            if (readData[i] != i + 1) {
+                qDebug("Second readData[%d] incorrect!  Expcted: %d  Actual: %d", i, i + 1, readData[i]);
+                return;
+            }
+        }
+        assertBufferSize(ringBuffer, 97);
+
+        // write 4 silent samples, 100 samples in buffer
+        if ((samplesWritten = ringBuffer.addSilentFrame(4) / sizeof(int16_t)) != 3) {
+            qDebug("addSilentFrame(4) incorrect!  Exptected: 3  Actual: %d", samplesWritten);
+            return;
+        }
+        assertBufferSize(ringBuffer, 100);
+
+        // read back 97 samples (the non-silent samples), 3 samples in buffer (expect to read "4" thru "100")
+        readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 97) / sizeof(int16_t);
+        for (int i = 3; i < 100; i++) {
+            if (readData[i] != i + 1) {
+                qDebug("third readData[%d] incorrect!  Expcted: %d  Actual: %d", i, i + 1, readData[i]);
+                return;
+            }
+        }
+        assertBufferSize(ringBuffer, 3);
+
+        // read back 3 silent samples, 0 samples in buffer
+        readIndexAt += ringBuffer.readSamples(&readData[readIndexAt], 3) / sizeof(int16_t);
+        for (int i = 100; i < 103; i++) {
+            if (readData[i] != 0) {
+                qDebug("Fourth readData[%d] incorrect!  Expcted: %d  Actual: %d", i, 0, readData[i]);
+                return;
+            }
+        }
+        assertBufferSize(ringBuffer, 0);
+    }
+
+    qDebug() << "PASSED";
+}
+
diff --git a/tests/audio/src/AudioRingBufferTests.h b/tests/audio/src/AudioRingBufferTests.h
new file mode 100644
index 0000000000..20cbe74699
--- /dev/null
+++ b/tests/audio/src/AudioRingBufferTests.h
@@ -0,0 +1,25 @@
+//
+//  AudioRingBufferTests.h
+//  tests/audio/src
+//
+//  Created by Yixin Wang on 6/24/2014
+//  Copyright 2014 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
+//
+
+#ifndef hifi_AudioRingBufferTests_h
+#define hifi_AudioRingBufferTests_h
+
+#include "AudioRingBuffer.h"
+
+
+namespace AudioRingBufferTests {
+
+    void runAllTests();
+
+    void assertBufferSize(const AudioRingBuffer& buffer, int samples);
+};
+
+#endif // hifi_AudioRingBufferTests_h
diff --git a/tests/audio/src/main.cpp b/tests/audio/src/main.cpp
new file mode 100644
index 0000000000..10f1a2e522
--- /dev/null
+++ b/tests/audio/src/main.cpp
@@ -0,0 +1,19 @@
+//
+//  main.cpp
+//  tests/audio/src
+//
+//  Copyright 2014 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
+//
+
+#include "AudioRingBufferTests.h"
+#include <stdio.h>
+
+int main(int argc, char** argv) {
+    AudioRingBufferTests::runAllTests();
+    printf("all tests passed.  press enter to exit\n");
+    getchar();
+    return 0;
+}
diff --git a/tests/metavoxels/CMakeLists.txt b/tests/metavoxels/CMakeLists.txt
index 5f11c7290e..ca141c5137 100644
--- a/tests/metavoxels/CMakeLists.txt
+++ b/tests/metavoxels/CMakeLists.txt
@@ -27,6 +27,7 @@ include_glm(${TARGET_NAME} "${ROOT_DIR}")
 # link in the shared libraries
 include(${MACRO_DIR}/LinkHifiLibrary.cmake)
 link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
+link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
 link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
 
 IF (WIN32)
diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp
index c9bce27cc3..61ab664310 100644
--- a/tests/metavoxels/src/MetavoxelTests.cpp
+++ b/tests/metavoxels/src/MetavoxelTests.cpp
@@ -28,10 +28,127 @@ MetavoxelTests::MetavoxelTests(int& argc, char** argv) :
     QCoreApplication(argc, argv) {
 }
 
+static bool testSpanList() {
+    SpanList list;
+
+    if (list.getTotalSet() != 0 || !list.getSpans().isEmpty()) {
+        qDebug() << "Failed empty state test.";
+        return true;
+    }
+    
+    if (list.set(-5, 15) != 10 || list.getTotalSet() != 0 || !list.getSpans().isEmpty()) {
+        qDebug() << "Failed initial front set.";
+        return true;
+    } 
+    
+    if (list.set(5, 15) != 0 || list.getTotalSet() != 15 || list.getSpans().size() != 1 ||
+            list.getSpans().at(0).unset != 5 || list.getSpans().at(0).set != 15) {
+        qDebug() << "Failed initial middle set.";
+        return true;
+    }
+    
+    if (list.set(25, 5) != 0 || list.getTotalSet() != 20 || list.getSpans().size() != 2 ||
+            list.getSpans().at(0).unset != 5 || list.getSpans().at(0).set != 15 ||
+            list.getSpans().at(1).unset != 5 || list.getSpans().at(1).set != 5) {
+        qDebug() << "Failed initial end set.";
+        return true;
+    }
+    
+    if (list.set(1, 3) != 0 || list.getTotalSet() != 23 || list.getSpans().size() != 3 ||
+            list.getSpans().at(0).unset != 1 || list.getSpans().at(0).set != 3 ||
+            list.getSpans().at(1).unset != 1 || list.getSpans().at(1).set != 15 ||
+            list.getSpans().at(2).unset != 5 || list.getSpans().at(2).set != 5) {
+        qDebug() << "Failed second front set.";
+        return true;
+    }
+    SpanList threeSet = list;
+    
+    if (list.set(20, 5) != 0 || list.getTotalSet() != 28 || list.getSpans().size() != 2 ||
+            list.getSpans().at(0).unset != 1 || list.getSpans().at(0).set != 3 ||
+            list.getSpans().at(1).unset != 1 || list.getSpans().at(1).set != 25) {
+        qDebug() << "Failed minimal join last two.";
+        return true;
+    }
+    
+    list = threeSet;
+    if (list.set(5, 25) != 0 || list.getTotalSet() != 28 || list.getSpans().size() != 2 ||
+            list.getSpans().at(0).unset != 1 || list.getSpans().at(0).set != 3 ||
+            list.getSpans().at(1).unset != 1 || list.getSpans().at(1).set != 25) {
+        qDebug() << "Failed maximal join last two.";
+        return true;
+    }
+    
+    list = threeSet;
+    if (list.set(10, 18) != 0 || list.getTotalSet() != 28 || list.getSpans().size() != 2 ||
+            list.getSpans().at(0).unset != 1 || list.getSpans().at(0).set != 3 ||
+            list.getSpans().at(1).unset != 1 || list.getSpans().at(1).set != 25) {
+        qDebug() << "Failed middle join last two.";
+        return true;
+    }
+    
+    list = threeSet;
+    if (list.set(10, 18) != 0 || list.getTotalSet() != 28 || list.getSpans().size() != 2 ||
+            list.getSpans().at(0).unset != 1 || list.getSpans().at(0).set != 3 ||
+            list.getSpans().at(1).unset != 1 || list.getSpans().at(1).set != 25) {
+        qDebug() << "Failed middle join last two.";
+        return true;
+    }
+    
+    list = threeSet;
+    if (list.set(2, 26) != 0 || list.getTotalSet() != 29 || list.getSpans().size() != 1 ||
+            list.getSpans().at(0).unset != 1 || list.getSpans().at(0).set != 29) {
+        qDebug() << "Failed middle join three.";
+        return true;
+    }
+    
+    list = threeSet;
+    if (list.set(0, 2) != 4 || list.getTotalSet() != 20 || list.getSpans().size() != 2 ||
+            list.getSpans().at(0).unset != 1 || list.getSpans().at(0).set != 15 ||
+            list.getSpans().at(1).unset != 5 || list.getSpans().at(1).set != 5) {
+        qDebug() << "Failed front advance.";
+        return true;
+    }
+    
+    list = threeSet;
+    if (list.set(-10, 15) != 20 || list.getTotalSet() != 5 || list.getSpans().size() != 1 ||
+            list.getSpans().at(0).unset != 5 || list.getSpans().at(0).set != 5) {
+        qDebug() << "Failed middle advance.";
+        return true;
+    }
+    
+    list = threeSet;
+    if (list.set(-10, 38) != 30 || list.getTotalSet() != 0 || list.getSpans().size() != 0) {
+        qDebug() << "Failed end advance.";
+        return true;
+    }
+    
+    list = threeSet;
+    if (list.set(-10, 100) != 90 || list.getTotalSet() != 0 || list.getSpans().size() != 0) {
+        qDebug() << "Failed clobber advance.";
+        return true;
+    }
+    
+    list = threeSet;
+    if (list.set(21, 3) != 0 || list.getTotalSet() != 26 || list.getSpans().size() != 4 ||
+            list.getSpans().at(0).unset != 1 || list.getSpans().at(0).set != 3 ||
+            list.getSpans().at(1).unset != 1 || list.getSpans().at(1).set != 15 ||
+            list.getSpans().at(2).unset != 1 || list.getSpans().at(2).set != 3 ||
+            list.getSpans().at(3).unset != 1 || list.getSpans().at(3).set != 5) {
+        qDebug() << "Failed adding fourth.";
+        return true;
+    }
+    
+    return false;
+}
+
 static int datagramsSent = 0;
 static int datagramsReceived = 0;
 static int bytesSent = 0;
 static int bytesReceived = 0;
+static int maxDatagramsPerPacket = 0;
+static int maxBytesPerPacket = 0;
+static int groupsSent = 0;
+static int maxPacketsPerGroup = 0;
 static int highPriorityMessagesSent = 0;
 static int highPriorityMessagesReceived = 0;
 static int unreliableMessagesSent = 0;
@@ -45,6 +162,8 @@ static int sharedObjectsDestroyed = 0;
 static int objectMutationsPerformed = 0;
 static int scriptObjectsCreated = 0;
 static int scriptMutationsPerformed = 0;
+static int metavoxelMutationsPerformed = 0;
+static int spannerMutationsPerformed = 0;
 
 static QByteArray createRandomBytes(int minimumSize, int maximumSize) {
     QByteArray bytes(randIntInRange(minimumSize, maximumSize), 0);
@@ -321,44 +440,122 @@ static bool testSerialization(Bitstream::MetadataType metadataType) {
 }
 
 bool MetavoxelTests::run() {
-    
-    qDebug() << "Running transmission tests...";
-    qDebug();
-    
+    LimitedNodeList::createInstance();
+
     // seed the random number generator so that our tests are reproducible
     srand(0xBAAAAABE);
 
-    // create two endpoints with the same header
-    QByteArray datagramHeader("testheader");
-    Endpoint alice(datagramHeader), bob(datagramHeader);
-    
-    alice.setOther(&bob);
-    bob.setOther(&alice);
-    
-    // perform a large number of simulation iterations
+    // check for an optional command line argument specifying a single test
+    QStringList arguments = this->arguments();
+    int test = (arguments.size() > 1) ? arguments.at(1).toInt() : 0;
+
+    if (test == 0 || test == 1) {
+        qDebug() << "Running SpanList test...";
+        qDebug();
+        
+        if (testSpanList()) {
+            return true;
+        }
+    }
+
     const int SIMULATION_ITERATIONS = 10000;
-    for (int i = 0; i < SIMULATION_ITERATIONS; i++) {
-        if (alice.simulate(i) || bob.simulate(i)) {
+    if (test == 0 || test == 2) {
+        qDebug() << "Running transmission test...";
+        qDebug();
+    
+        // create two endpoints with the same header
+        TestEndpoint alice, bob;
+        
+        alice.setOther(&bob);
+        bob.setOther(&alice);
+        
+        // perform a large number of simulation iterations
+        for (int i = 0; i < SIMULATION_ITERATIONS; i++) {
+            if (alice.simulate(i) || bob.simulate(i)) {
+                return true;
+            }
+        }
+        
+        qDebug() << "Sent" << highPriorityMessagesSent << "high priority messages, received" << highPriorityMessagesReceived;
+        qDebug() << "Sent" << unreliableMessagesSent << "unreliable messages, received" << unreliableMessagesReceived;
+        qDebug() << "Sent" << reliableMessagesSent << "reliable messages, received" << reliableMessagesReceived;
+        qDebug() << "Sent" << streamedBytesSent << "streamed bytes, received" << streamedBytesReceived;
+        qDebug() << "Sent" << datagramsSent << "datagrams with" << bytesSent << "bytes, received" <<
+            datagramsReceived << "with" << bytesReceived << "bytes";
+        qDebug() << "Max" << maxDatagramsPerPacket << "datagrams," << maxBytesPerPacket << "bytes per packet";
+        qDebug() << "Created" << sharedObjectsCreated << "shared objects, destroyed" << sharedObjectsDestroyed;
+        qDebug() << "Performed" << objectMutationsPerformed << "object mutations";
+        qDebug() << "Created" << scriptObjectsCreated << "script objects, mutated" << scriptMutationsPerformed;
+        qDebug();
+    }
+    
+    if (test == 0 || test == 3) {
+        qDebug() << "Running congestion control test...";
+        qDebug();
+        
+        // clear the stats
+        streamedBytesSent = streamedBytesReceived = datagramsSent = bytesSent = 0;
+        datagramsReceived = bytesReceived = maxDatagramsPerPacket = maxBytesPerPacket = 0;
+        
+        // create two endpoints with the same header
+        TestEndpoint alice(TestEndpoint::CONGESTION_MODE), bob(TestEndpoint::CONGESTION_MODE);
+        
+        alice.setOther(&bob);
+        bob.setOther(&alice);
+        
+        // perform a large number of simulation iterations
+        for (int i = 0; i < SIMULATION_ITERATIONS; i++) {
+            if (alice.simulate(i) || bob.simulate(i)) {
+                return true;
+            }
+        }
+        
+        qDebug() << "Sent" << streamedBytesSent << "streamed bytes, received" << streamedBytesReceived;
+        qDebug() << "Sent" << datagramsSent << "datagrams in" << groupsSent << "groups with" << bytesSent <<
+            "bytes, received" << datagramsReceived << "with" << bytesReceived << "bytes";
+        qDebug() << "Max" << maxDatagramsPerPacket << "datagrams," << maxBytesPerPacket << "bytes per packet";
+        qDebug() << "Max" << maxPacketsPerGroup << "packets per group";
+        qDebug() << "Average" << (bytesReceived / datagramsReceived) << "bytes per datagram," <<
+            (datagramsSent / groupsSent) << "datagrams per group";
+        qDebug() << "Speed:" << (bytesReceived / SIMULATION_ITERATIONS) << "bytes per iteration";
+        qDebug() << "Efficiency:" << ((float)streamedBytesReceived / bytesReceived);
+    }
+    
+    if (test == 0 || test == 4) {
+        qDebug() << "Running serialization test...";
+        qDebug();
+        
+        if (testSerialization(Bitstream::HASH_METADATA) || testSerialization(Bitstream::FULL_METADATA)) {
             return true;
         }
     }
     
-    qDebug() << "Sent" << highPriorityMessagesSent << "high priority messages, received" << highPriorityMessagesReceived;
-    qDebug() << "Sent" << unreliableMessagesSent << "unreliable messages, received" << unreliableMessagesReceived;
-    qDebug() << "Sent" << reliableMessagesSent << "reliable messages, received" << reliableMessagesReceived;
-    qDebug() << "Sent" << streamedBytesSent << "streamed bytes, received" << streamedBytesReceived;
-    qDebug() << "Sent" << datagramsSent << "datagrams with" << bytesSent << "bytes, received" <<
-        datagramsReceived << "with" << bytesReceived << "bytes";
-    qDebug() << "Created" << sharedObjectsCreated << "shared objects, destroyed" << sharedObjectsDestroyed;
-    qDebug() << "Performed" << objectMutationsPerformed << "object mutations";
-    qDebug() << "Created" << scriptObjectsCreated << "script objects, mutated" << scriptMutationsPerformed;
-    qDebug();
+    if (test == 0 || test == 5) {
+        qDebug() << "Running metavoxel data test...";
+        qDebug();
     
-    qDebug() << "Running serialization tests...";
-    qDebug();
+        // clear the stats
+        datagramsSent = bytesSent = datagramsReceived = bytesReceived = maxDatagramsPerPacket = maxBytesPerPacket = 0;
     
-    if (testSerialization(Bitstream::HASH_METADATA) || testSerialization(Bitstream::FULL_METADATA)) {
-        return true;
+        // create client and server endpoints
+        TestEndpoint client(TestEndpoint::METAVOXEL_CLIENT_MODE);
+        TestEndpoint server(TestEndpoint::METAVOXEL_SERVER_MODE);
+        
+        client.setOther(&server);
+        server.setOther(&client);
+        
+        // simulate
+        for (int i = 0; i < SIMULATION_ITERATIONS; i++) {
+            if (client.simulate(i) || server.simulate(i)) {
+                return true;
+            }
+        }
+        
+        qDebug() << "Sent" << datagramsSent << "datagrams with" << bytesSent << "bytes, received" <<
+            datagramsReceived << "with" << bytesReceived << "bytes";
+        qDebug() << "Max" << maxDatagramsPerPacket << "datagrams," << maxBytesPerPacket << "bytes per packet";
+        qDebug() << "Performed" << metavoxelMutationsPerformed << "metavoxel mutations," << spannerMutationsPerformed <<
+            "spanner mutations";
     }
     
     qDebug() << "All tests passed!";
@@ -375,47 +572,137 @@ static SharedObjectPointer createRandomSharedObject() {
     }
 }
 
-Endpoint::Endpoint(const QByteArray& datagramHeader) :
-    _sequencer(new DatagramSequencer(datagramHeader, this)),
+class RandomVisitor : public MetavoxelVisitor {
+public:
+    
+    int leafCount;
+    
+    RandomVisitor();
+    virtual int visit(MetavoxelInfo& info);
+};
+
+RandomVisitor::RandomVisitor() :
+    MetavoxelVisitor(QVector<AttributePointer>(),
+        QVector<AttributePointer>() << AttributeRegistry::getInstance()->getColorAttribute()),
+    leafCount(0) {
+}
+
+const float MAXIMUM_LEAF_SIZE = 0.5f;
+const float MINIMUM_LEAF_SIZE = 0.25f;
+
+int RandomVisitor::visit(MetavoxelInfo& info) {
+    if (info.size > MAXIMUM_LEAF_SIZE || (info.size > MINIMUM_LEAF_SIZE && randomBoolean())) {
+        return DEFAULT_ORDER;
+    }
+    info.outputValues[0] = OwnedAttributeValue(_outputs.at(0), encodeInline<QRgb>(qRgb(randomColorValue(),
+        randomColorValue(), randomColorValue())));
+    leafCount++;
+    return STOP_RECURSION;
+}
+
+class TestSendRecord : public PacketRecord {
+public:
+    
+    TestSendRecord(const MetavoxelLOD& lod = MetavoxelLOD(), const MetavoxelData& data = MetavoxelData(),
+        const SharedObjectPointer& localState = SharedObjectPointer(), int packetNumber = 0);
+    
+    const SharedObjectPointer& getLocalState() const { return _localState; }
+    int getPacketNumber() const { return _packetNumber; }
+    
+private:
+    
+    SharedObjectPointer _localState;
+    int _packetNumber;
+    
+};
+
+TestSendRecord::TestSendRecord(const MetavoxelLOD& lod, const MetavoxelData& data,
+        const SharedObjectPointer& localState, int packetNumber) :
+    PacketRecord(lod, data),
+    _localState(localState),
+    _packetNumber(packetNumber) {
+}
+
+class TestReceiveRecord : public PacketRecord {
+public:
+    
+    TestReceiveRecord(const MetavoxelLOD& lod = MetavoxelLOD(), const MetavoxelData& data = MetavoxelData(),
+        const SharedObjectPointer& remoteState = SharedObjectPointer());
+
+    const SharedObjectPointer& getRemoteState() const { return _remoteState; }
+
+private:
+    
+    SharedObjectPointer _remoteState;
+};
+
+TestReceiveRecord::TestReceiveRecord(const MetavoxelLOD& lod,
+        const MetavoxelData& data, const SharedObjectPointer& remoteState) :
+    PacketRecord(lod, data),
+    _remoteState(remoteState) {
+}
+
+TestEndpoint::TestEndpoint(Mode mode) :
+    Endpoint(SharedNodePointer(), new TestSendRecord(), new TestReceiveRecord()),
+    _mode(mode),
     _highPriorityMessagesToSend(0.0f),
     _reliableMessagesToSend(0.0f) {
     
-    connect(_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendDatagram(const QByteArray&)));
-    connect(_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readMessage(Bitstream&)));
-    connect(_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)),
+    connect(&_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)),
         SLOT(handleHighPriorityMessage(const QVariant&)));
     
-    connect(_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(clearSendRecordsBefore(int)));
-    connect(_sequencer, SIGNAL(receiveAcknowledged(int)), SLOT(clearReceiveRecordsBefore(int)));
-    
-    // insert the baseline send record
-    SendRecord sendRecord = { 0 };
-    _sendRecords.append(sendRecord);
-    
-    // insert the baseline receive record
-    ReceiveRecord receiveRecord = { 0 };
-    _receiveRecords.append(receiveRecord);
-    
+    if (mode == METAVOXEL_CLIENT_MODE) {
+        _lod = MetavoxelLOD(glm::vec3(), 0.01f);
+        return;
+    }
+    if (mode == METAVOXEL_SERVER_MODE) {
+        _data.expand();
+        _data.expand();
+        
+        RandomVisitor visitor;
+        _data.guide(visitor);
+        qDebug() << "Created" << visitor.leafCount << "base leaves";
+        
+        _data.insert(AttributeRegistry::getInstance()->getSpannersAttribute(), new Sphere());
+        
+        _sphere = new Sphere();
+        static_cast<Transformable*>(_sphere.data())->setScale(0.01f);
+        _data.insert(AttributeRegistry::getInstance()->getSpannersAttribute(), _sphere);
+        return;
+    }
     // create the object that represents out delta-encoded state
     _localState = new TestSharedObjectA();
     
-    connect(_sequencer->getReliableInputChannel(), SIGNAL(receivedMessage(const QVariant&)),
+    connect(_sequencer.getReliableInputChannel(), SIGNAL(receivedMessage(const QVariant&)),
         SLOT(handleReliableMessage(const QVariant&)));
     
-    ReliableChannel* secondInput = _sequencer->getReliableInputChannel(1);
+    ReliableChannel* secondInput = _sequencer.getReliableInputChannel(1);
     secondInput->setMessagesEnabled(false);
     connect(&secondInput->getBuffer(), SIGNAL(readyRead()), SLOT(readReliableChannel()));
     
     // enqueue a large amount of data in a low-priority channel
-    ReliableChannel* output = _sequencer->getReliableOutputChannel(1);
+    ReliableChannel* output = _sequencer.getReliableOutputChannel(1);
     output->setPriority(0.25f);
     output->setMessagesEnabled(false);
-    const int MIN_STREAM_BYTES = 100000;
-    const int MAX_STREAM_BYTES = 200000;
-    QByteArray bytes = createRandomBytes(MIN_STREAM_BYTES, MAX_STREAM_BYTES);
+    QByteArray bytes;
+    if (mode == CONGESTION_MODE) {
+        const int HUGE_STREAM_BYTES = 60 * 1024 * 1024;
+        bytes = createRandomBytes(HUGE_STREAM_BYTES, HUGE_STREAM_BYTES);
+        
+        // initialize the pipeline
+        for (int i = 0; i < 10; i++) {
+            _pipeline.append(ByteArrayVector());
+        }
+        _remainingPipelineCapacity = 100 * 1024;
+        
+    } else {
+        const int MIN_STREAM_BYTES = 100000;
+        const int MAX_STREAM_BYTES = 200000;
+        bytes = createRandomBytes(MIN_STREAM_BYTES, MAX_STREAM_BYTES);
+    }
     _dataStreamed.append(bytes);
     output->getBuffer().write(bytes);
-    streamedBytesSent += bytes.size();
+    streamedBytesSent += bytes.size();    
 }
 
 static QVariant createRandomMessage() {
@@ -512,12 +799,42 @@ static bool messagesEqual(const QVariant& firstMessage, const QVariant& secondMe
     }
 }
 
-bool Endpoint::simulate(int iterationNumber) {
+class MutateVisitor : public MetavoxelVisitor {
+public:
+    
+    MutateVisitor();
+    virtual int visit(MetavoxelInfo& info);
+
+private:
+    
+    int _mutationsRemaining;
+};
+
+MutateVisitor::MutateVisitor() :
+    MetavoxelVisitor(QVector<AttributePointer>(),
+        QVector<AttributePointer>() << AttributeRegistry::getInstance()->getColorAttribute()),
+    _mutationsRemaining(randIntInRange(2, 4)) {
+}
+
+int MutateVisitor::visit(MetavoxelInfo& info) {
+    if (_mutationsRemaining <= 0) {
+        return STOP_RECURSION;
+    }
+    if (info.size > MAXIMUM_LEAF_SIZE || (info.size > MINIMUM_LEAF_SIZE && randomBoolean())) {
+        return encodeRandomOrder();
+    }
+    info.outputValues[0] = OwnedAttributeValue(_outputs.at(0), encodeInline<QRgb>(qRgb(randomColorValue(),
+        randomColorValue(), randomColorValue())));
+    _mutationsRemaining--;
+    metavoxelMutationsPerformed++;
+    return STOP_RECURSION;
+}
+
+bool TestEndpoint::simulate(int iterationNumber) {
     // update/send our delayed datagrams
-    for (QList<QPair<QByteArray, int> >::iterator it = _delayedDatagrams.begin(); it != _delayedDatagrams.end(); ) {
+    for (QList<ByteArrayIntPair>::iterator it = _delayedDatagrams.begin(); it != _delayedDatagrams.end(); ) {
         if (it->second-- == 1) {
-            _other->_sequencer->receivedDatagram(it->first);
-            datagramsReceived++;    
+            _other->parseData(it->first);
             it = _delayedDatagrams.erase(it);
         
         } else {
@@ -525,86 +842,272 @@ bool Endpoint::simulate(int iterationNumber) {
         }
     }
 
-    // enqueue some number of high priority messages
-    const float MIN_HIGH_PRIORITY_MESSAGES = 0.0f;
-    const float MAX_HIGH_PRIORITY_MESSAGES = 2.0f;
-    _highPriorityMessagesToSend += randFloatInRange(MIN_HIGH_PRIORITY_MESSAGES, MAX_HIGH_PRIORITY_MESSAGES);   
-    while (_highPriorityMessagesToSend >= 1.0f) {
-        QVariant message = createRandomMessage();
-        _highPriorityMessagesSent.append(message);
-        _sequencer->sendHighPriorityMessage(message);
-        highPriorityMessagesSent++;
-        _highPriorityMessagesToSend -= 1.0f;
+    int oldDatagramsSent = datagramsSent;
+    int oldBytesSent = bytesSent;
+    if (_mode == CONGESTION_MODE) {
+        // cycle our pipeline
+        ByteArrayVector datagrams = _pipeline.takeLast();
+        _pipeline.prepend(ByteArrayVector());
+        foreach (const QByteArray& datagram, datagrams) {
+            _sequencer.receivedDatagram(datagram);
+            datagramsReceived++;
+            bytesReceived += datagram.size();
+            _remainingPipelineCapacity += datagram.size();
+        }
+        int packetCount = _sequencer.startPacketGroup();
+        groupsSent++;
+        maxPacketsPerGroup = qMax(maxPacketsPerGroup, packetCount);
+        for (int i = 0; i < packetCount; i++) {
+            oldDatagramsSent = datagramsSent;
+            oldBytesSent = bytesSent;
+            
+            Bitstream& out = _sequencer.startPacket();
+            out << QVariant();
+            _sequencer.endPacket();
+            
+            maxDatagramsPerPacket = qMax(maxDatagramsPerPacket, datagramsSent - oldDatagramsSent);
+            maxBytesPerPacket = qMax(maxBytesPerPacket, bytesSent - oldBytesSent);
+            
+            // record the send
+            _sendRecords.append(maybeCreateSendRecord());
+        }
+        return false;
+        
+    } else if (_mode == METAVOXEL_CLIENT_MODE) {
+        Bitstream& out = _sequencer.startPacket();
+    
+        ClientStateMessage state = { _lod };
+        out << QVariant::fromValue(state);
+        _sequencer.endPacket();
+        
+        // record the send
+        _sendRecords.append(maybeCreateSendRecord());
+
+    } else if (_mode == METAVOXEL_SERVER_MODE) {
+        // make a random change
+        MutateVisitor visitor;
+        _data.guide(visitor);
+        
+        // perhaps mutate the spanner
+        if (randomBoolean()) {
+            SharedObjectPointer oldSphere = _sphere;
+            _sphere = _sphere->clone(true);
+            Sphere* newSphere = static_cast<Sphere*>(_sphere.data());
+            if (randomBoolean()) {
+                newSphere->setColor(QColor(randomColorValue(), randomColorValue(), randomColorValue()));
+            } else {
+                newSphere->setTranslation(newSphere->getTranslation() + glm::vec3(randFloatInRange(-0.01f, 0.01f),
+                    randFloatInRange(-0.01f, 0.01f), randFloatInRange(-0.01f, 0.01f)));
+            }
+            _data.replace(AttributeRegistry::getInstance()->getSpannersAttribute(), oldSphere, _sphere);
+            spannerMutationsPerformed++;
+        }
+        
+        // wait until we have a valid lod before sending
+        if (!_lod.isValid()) {
+            return false;
+        }
+        Bitstream& out = _sequencer.startPacket();
+        out << QVariant::fromValue(MetavoxelDeltaMessage());
+        PacketRecord* sendRecord = getLastAcknowledgedSendRecord();
+        _data.writeDelta(sendRecord->getData(), sendRecord->getLOD(), out, _lod);
+        _sequencer.endPacket();
+       
+        // record the send
+        _sendRecords.append(maybeCreateSendRecord());
+         
+    } else {
+        // enqueue some number of high priority messages
+        const float MIN_HIGH_PRIORITY_MESSAGES = 0.0f;
+        const float MAX_HIGH_PRIORITY_MESSAGES = 2.0f;
+        _highPriorityMessagesToSend += randFloatInRange(MIN_HIGH_PRIORITY_MESSAGES, MAX_HIGH_PRIORITY_MESSAGES);   
+        while (_highPriorityMessagesToSend >= 1.0f) {
+            QVariant message = createRandomMessage();
+            _highPriorityMessagesSent.append(message);
+            _sequencer.sendHighPriorityMessage(message);
+            highPriorityMessagesSent++;
+            _highPriorityMessagesToSend -= 1.0f;
+        }
+        
+        // and some number of reliable messages
+        const float MIN_RELIABLE_MESSAGES = 0.0f;
+        const float MAX_RELIABLE_MESSAGES = 4.0f;
+        _reliableMessagesToSend += randFloatInRange(MIN_RELIABLE_MESSAGES, MAX_RELIABLE_MESSAGES);   
+        while (_reliableMessagesToSend >= 1.0f) {
+            QVariant message = createRandomMessage();
+            _reliableMessagesSent.append(message);
+            _sequencer.getReliableOutputChannel()->sendMessage(message);
+            reliableMessagesSent++;
+            _reliableMessagesToSend -= 1.0f;
+        }
+        
+        // tweak the local state
+        _localState = mutate(_localState);
+        
+        // send a packet
+        try {
+            Bitstream& out = _sequencer.startPacket();
+            SequencedTestMessage message = { iterationNumber, createRandomMessage(), _localState };
+            _unreliableMessagesSent.append(message);
+            unreliableMessagesSent++;
+            out << message;
+            _sequencer.endPacket();
+        
+        } catch (const QString& message) {
+            qDebug() << message;
+            return true;
+        }
+    
+        // record the send
+        _sendRecords.append(maybeCreateSendRecord());
     }
-    
-    // and some number of reliable messages
-    const float MIN_RELIABLE_MESSAGES = 0.0f;
-    const float MAX_RELIABLE_MESSAGES = 4.0f;
-    _reliableMessagesToSend += randFloatInRange(MIN_RELIABLE_MESSAGES, MAX_RELIABLE_MESSAGES);   
-    while (_reliableMessagesToSend >= 1.0f) {
-        QVariant message = createRandomMessage();
-        _reliableMessagesSent.append(message);
-        _sequencer->getReliableOutputChannel()->sendMessage(message);
-        reliableMessagesSent++;
-        _reliableMessagesToSend -= 1.0f;
-    }
-    
-    // tweak the local state
-    _localState = mutate(_localState);
-    
-    // send a packet
-    try {
-        Bitstream& out = _sequencer->startPacket();
-        SequencedTestMessage message = { iterationNumber, createRandomMessage(), _localState };
-        _unreliableMessagesSent.append(message);
-        unreliableMessagesSent++;
-        out << message;
-        _sequencer->endPacket();
-    
-    } catch (const QString& message) {
-        qDebug() << message;
-        return true;
-    }
-    
-    // record the send
-    SendRecord record = { _sequencer->getOutgoingPacketNumber(), _localState };
-    _sendRecords.append(record);
-    
+    maxDatagramsPerPacket = qMax(maxDatagramsPerPacket, datagramsSent - oldDatagramsSent);
+    maxBytesPerPacket = qMax(maxBytesPerPacket, bytesSent - oldBytesSent);
     return false;
 }
 
-void Endpoint::sendDatagram(const QByteArray& datagram) {
+int TestEndpoint::parseData(const QByteArray& packet) {
+    if (_mode == CONGESTION_MODE) {
+        if (packet.size() <= _remainingPipelineCapacity) {
+            // have to copy the datagram; the one we're passed is a reference to a shared buffer
+            _pipeline[0].append(QByteArray(packet.constData(), packet.size()));
+            _remainingPipelineCapacity -= packet.size();   
+        }
+    } else {
+        _sequencer.receivedDatagram(packet);
+        datagramsReceived++;
+        bytesReceived += packet.size();
+    }
+    return packet.size();
+}
+
+void TestEndpoint::sendDatagram(const QByteArray& datagram) {
     datagramsSent++;
     bytesSent += datagram.size();
     
     // some datagrams are dropped
     const float DROP_PROBABILITY = 0.1f;
-    if (randFloat() < DROP_PROBABILITY) {
+    float probabilityMultiplier = (_mode == CONGESTION_MODE) ? 0.01f : 1.0f;
+    if (randFloat() < DROP_PROBABILITY * probabilityMultiplier) {
         return;
     }
     
     // some are received out of order
     const float REORDER_PROBABILITY = 0.1f;
-    if (randFloat() < REORDER_PROBABILITY) {
+    if (randFloat() < REORDER_PROBABILITY * probabilityMultiplier) {
         const int MIN_DELAY = 1;
         const int MAX_DELAY = 5;
         // have to copy the datagram; the one we're passed is a reference to a shared buffer
-        _delayedDatagrams.append(QPair<QByteArray, int>(QByteArray(datagram.constData(), datagram.size()),
+        _delayedDatagrams.append(ByteArrayIntPair(QByteArray(datagram.constData(), datagram.size()),
             randIntInRange(MIN_DELAY, MAX_DELAY)));
         
         // and some are duplicated
         const float DUPLICATE_PROBABILITY = 0.01f;
-        if (randFloat() > DUPLICATE_PROBABILITY) {
+        if (randFloat() > DUPLICATE_PROBABILITY * probabilityMultiplier) {
             return;
         }
     }
     
-    _other->_sequencer->receivedDatagram(datagram);
-    datagramsReceived++;
-    bytesReceived += datagram.size();
+    _other->parseData(datagram);
 }
 
-void Endpoint::handleHighPriorityMessage(const QVariant& message) {
+void TestEndpoint::readMessage(Bitstream& in) {
+    if (_mode == CONGESTION_MODE) {
+        QVariant message;
+        in >> message;
+        
+        // record the receipt
+        _receiveRecords.append(maybeCreateReceiveRecord());
+        return;
+    }
+    if (_mode == METAVOXEL_CLIENT_MODE) {
+        QVariant message;
+        in >> message;
+        handleMessage(message, in);
+    
+        // deep-compare data to sent version
+        int packetNumber = _sequencer.getIncomingPacketNumber();
+        foreach (PacketRecord* record, _other->_sendRecords) {
+            TestSendRecord* sendRecord = static_cast<TestSendRecord*>(record);
+            if (sendRecord->getPacketNumber() == packetNumber) {
+                if (!sendRecord->getData().deepEquals(_data, getLastAcknowledgedSendRecord()->getLOD())) {
+                    qDebug() << "Sent/received metavoxel data mismatch.";
+                    exit(true);
+                }
+                break;
+            }
+        }
+        
+        // record the receipt
+        _receiveRecords.append(maybeCreateReceiveRecord());
+        return;
+    }
+    if (_mode == METAVOXEL_SERVER_MODE) {
+        QVariant message;
+        in >> message;
+        handleMessage(message, in);
+        
+        // record the receipt
+        _receiveRecords.append(maybeCreateReceiveRecord());
+        return;
+    }
+
+    SequencedTestMessage message;
+    in >> message;
+    
+    _remoteState = message.state;
+    
+    // record the receipt
+    _receiveRecords.append(maybeCreateReceiveRecord());
+    
+    for (QList<SequencedTestMessage>::iterator it = _other->_unreliableMessagesSent.begin();
+            it != _other->_unreliableMessagesSent.end(); it++) {
+        if (it->sequenceNumber == message.sequenceNumber) {
+            if (!messagesEqual(it->submessage, message.submessage)) {
+                qDebug() << "Sent/received unreliable message mismatch.";
+                exit(true);
+            }
+            if (!it->state->equals(message.state)) {
+                qDebug() << "Delta-encoded object mismatch.";
+                exit(true);
+            }
+            _other->_unreliableMessagesSent.erase(_other->_unreliableMessagesSent.begin(), it + 1);
+            unreliableMessagesReceived++;
+            return;
+        }
+    }
+    qDebug() << "Received unsent/already sent unreliable message.";
+    exit(true);
+}
+
+void TestEndpoint::handleMessage(const QVariant& message, Bitstream& in) {
+    int userType = message.userType();
+    if (userType == ClientStateMessage::Type) {
+        ClientStateMessage state = message.value<ClientStateMessage>();
+        _lod = state.lod;
+    
+    } else if (userType == MetavoxelDeltaMessage::Type) {
+        PacketRecord* receiveRecord = getLastAcknowledgedReceiveRecord();
+        _data.readDelta(receiveRecord->getData(), receiveRecord->getLOD(), in, getLastAcknowledgedSendRecord()->getLOD());
+    
+    } else if (userType == QMetaType::QVariantList) {
+        foreach (const QVariant& element, message.toList()) {
+            handleMessage(element, in);
+        }
+    }
+}
+
+PacketRecord* TestEndpoint::maybeCreateSendRecord() const {
+    return new TestSendRecord(_lod, (_mode == METAVOXEL_CLIENT_MODE) ? MetavoxelData() : _data,
+        _localState, _sequencer.getOutgoingPacketNumber());
+}
+
+PacketRecord* TestEndpoint::maybeCreateReceiveRecord() const {
+    return new TestReceiveRecord(getLastAcknowledgedSendRecord()->getLOD(),
+        (_mode == METAVOXEL_SERVER_MODE) ? MetavoxelData() : _data, _remoteState);
+}
+
+void TestEndpoint::handleHighPriorityMessage(const QVariant& message) {
     if (message.userType() == ClearSharedObjectMessage::Type) {
         return;
     }
@@ -618,34 +1121,7 @@ void Endpoint::handleHighPriorityMessage(const QVariant& message) {
     highPriorityMessagesReceived++;
 }
 
-void Endpoint::readMessage(Bitstream& in) {
-    SequencedTestMessage message;
-    in >> message;
-    
-    _remoteState = message.state;
-    
-    // record the receipt
-    ReceiveRecord record = { _sequencer->getIncomingPacketNumber(), message.state };
-    _receiveRecords.append(record);
-    
-    for (QList<SequencedTestMessage>::iterator it = _other->_unreliableMessagesSent.begin();
-            it != _other->_unreliableMessagesSent.end(); it++) {
-        if (it->sequenceNumber == message.sequenceNumber) {
-            if (!messagesEqual(it->submessage, message.submessage)) {
-                throw QString("Sent/received unreliable message mismatch.");
-            }
-            if (!it->state->equals(message.state)) {
-                throw QString("Delta-encoded object mismatch.");
-            }
-            _other->_unreliableMessagesSent.erase(_other->_unreliableMessagesSent.begin(), it + 1);
-            unreliableMessagesReceived++;
-            return;
-        }
-    }
-    throw QString("Received unsent/already sent unreliable message.");
-}
-
-void Endpoint::handleReliableMessage(const QVariant& message) {
+void TestEndpoint::handleReliableMessage(const QVariant& message) {
     if (message.userType() == ClearSharedObjectMessage::Type ||
             message.userType() == ClearMainChannelSharedObjectMessage::Type) {
         return;
@@ -660,8 +1136,8 @@ void Endpoint::handleReliableMessage(const QVariant& message) {
     reliableMessagesReceived++;
 }
 
-void Endpoint::readReliableChannel() {
-    CircularBuffer& buffer = _sequencer->getReliableInputChannel(1)->getBuffer();
+void TestEndpoint::readReliableChannel() {
+    CircularBuffer& buffer = _sequencer.getReliableInputChannel(1)->getBuffer();
     QByteArray bytes = buffer.read(buffer.bytesAvailable());
     if (_other->_dataStreamed.size() < bytes.size()) {
         throw QString("Received unsent/already sent streamed data.");
@@ -674,14 +1150,6 @@ void Endpoint::readReliableChannel() {
     streamedBytesReceived += bytes.size();
 }
 
-void Endpoint::clearSendRecordsBefore(int index) {
-    _sendRecords.erase(_sendRecords.begin(), _sendRecords.begin() + index + 1);
-}
-
-void Endpoint::clearReceiveRecordsBefore(int index) {
-    _receiveRecords.erase(_receiveRecords.begin(), _receiveRecords.begin() + index + 1);
-}
-
 TestSharedObjectA::TestSharedObjectA(float foo, TestEnum baz, TestFlags bong) :
         _foo(foo),
         _baz(baz),
diff --git a/tests/metavoxels/src/MetavoxelTests.h b/tests/metavoxels/src/MetavoxelTests.h
index ac9eda2659..476a8c6295 100644
--- a/tests/metavoxels/src/MetavoxelTests.h
+++ b/tests/metavoxels/src/MetavoxelTests.h
@@ -15,7 +15,7 @@
 #include <QCoreApplication>
 #include <QVariantList>
 
-#include <DatagramSequencer.h>
+#include <Endpoint.h>
 #include <ScriptCache.h>
 
 class SequencedTestMessage;
@@ -34,53 +34,60 @@ public:
 };
 
 /// Represents a simulated endpoint.
-class Endpoint : public QObject {
+class TestEndpoint : public Endpoint {
     Q_OBJECT
 
 public:
     
-    Endpoint(const QByteArray& datagramHeader);
+    enum Mode { BASIC_PEER_MODE, CONGESTION_MODE, METAVOXEL_SERVER_MODE, METAVOXEL_CLIENT_MODE };
+    
+    TestEndpoint(Mode mode = BASIC_PEER_MODE);
 
-    void setOther(Endpoint* other) { _other = other; }
+    void setOther(TestEndpoint* other) { _other = other; }
 
     /// Perform a simulation step.
     /// \return true if failure was detected
     bool simulate(int iterationNumber);
 
-private slots:
+    virtual int parseData(const QByteArray& packet);
+    
+protected:
 
-    void sendDatagram(const QByteArray& datagram);    
+    virtual void sendDatagram(const QByteArray& data);
+    virtual void readMessage(Bitstream& in);
+    
+    virtual void handleMessage(const QVariant& message, Bitstream& in); 
+    
+    virtual PacketRecord* maybeCreateSendRecord() const;
+    virtual PacketRecord* maybeCreateReceiveRecord() const;
+    
+private slots:
+   
     void handleHighPriorityMessage(const QVariant& message);
-    void readMessage(Bitstream& in);
     void handleReliableMessage(const QVariant& message);
     void readReliableChannel();
 
-    void clearSendRecordsBefore(int index);    
-    void clearReceiveRecordsBefore(int index);
-
 private:
     
-    class SendRecord {
-    public:
-        int packetNumber;
-        SharedObjectPointer localState;
-    };
-    
-    class ReceiveRecord {
-    public:
-        int packetNumber;
-        SharedObjectPointer remoteState;
-    };
-    
-    DatagramSequencer* _sequencer;
-    QList<SendRecord> _sendRecords;
-    QList<ReceiveRecord> _receiveRecords;
+    Mode _mode;
     
     SharedObjectPointer _localState;
     SharedObjectPointer _remoteState;
     
-    Endpoint* _other;
-    QList<QPair<QByteArray, int> > _delayedDatagrams;
+    MetavoxelData _data;
+    MetavoxelLOD _lod;
+    
+    SharedObjectPointer _sphere;
+    
+    TestEndpoint* _other;
+    
+    typedef QPair<QByteArray, int> ByteArrayIntPair;
+    QList<ByteArrayIntPair> _delayedDatagrams;
+
+    typedef QVector<QByteArray> ByteArrayVector;
+    QList<ByteArrayVector> _pipeline;
+    int _remainingPipelineCapacity;
+
     float _highPriorityMessagesToSend;
     QVariantList _highPriorityMessagesSent;
     QList<SequencedTestMessage> _unreliableMessagesSent;
diff --git a/tests/networking/CMakeLists.txt b/tests/networking/CMakeLists.txt
new file mode 100644
index 0000000000..2e094d2ce7
--- /dev/null
+++ b/tests/networking/CMakeLists.txt
@@ -0,0 +1,39 @@
+cmake_minimum_required(VERSION 2.8)
+
+if (WIN32)
+  cmake_policy (SET CMP0020 NEW)
+endif (WIN32)
+
+set(TARGET_NAME networking-tests)
+
+set(ROOT_DIR ../..)
+set(MACRO_DIR ${ROOT_DIR}/cmake/macros)
+
+# setup for find modules
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/")
+
+#find_package(Qt5Network REQUIRED)
+#find_package(Qt5Script REQUIRED)
+#find_package(Qt5Widgets REQUIRED)
+
+include(${MACRO_DIR}/SetupHifiProject.cmake)
+setup_hifi_project(${TARGET_NAME} TRUE)
+
+include(${MACRO_DIR}/AutoMTC.cmake)
+auto_mtc(${TARGET_NAME} ${ROOT_DIR})
+
+#qt5_use_modules(${TARGET_NAME} Network Script Widgets)
+
+#include glm
+include(${MACRO_DIR}/IncludeGLM.cmake)
+include_glm(${TARGET_NAME} ${ROOT_DIR})
+
+# link in the shared libraries
+include(${MACRO_DIR}/LinkHifiLibrary.cmake)
+link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR})
+link_hifi_library(networking ${TARGET_NAME} ${ROOT_DIR})
+
+IF (WIN32)
+    target_link_libraries(${TARGET_NAME} Winmm Ws2_32)
+ENDIF(WIN32)
+
diff --git a/tests/networking/src/SequenceNumberStatsTests.cpp b/tests/networking/src/SequenceNumberStatsTests.cpp
new file mode 100644
index 0000000000..89a14deb20
--- /dev/null
+++ b/tests/networking/src/SequenceNumberStatsTests.cpp
@@ -0,0 +1,267 @@
+//
+//  AudioRingBufferTests.cpp
+//  tests/networking/src
+//
+//  Created by Yixin Wang on 6/24/2014
+//  Copyright 2014 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
+//
+
+#include "SequenceNumberStatsTests.h"
+
+#include "SharedUtil.h"
+#include <limits>
+
+
+void SequenceNumberStatsTests::runAllTests() {
+
+    rolloverTest();
+    earlyLateTest();
+    duplicateTest();
+    pruneTest();
+}
+
+const int UINT16_RANGE = std::numeric_limits<quint16>::max() + 1;
+
+
+void SequenceNumberStatsTests::rolloverTest() {
+
+    SequenceNumberStats stats;
+
+    // insert enough samples to cause 3 rollovers
+    quint16 seq = 79;   // start on some random number
+
+    for (int R = 0; R < 2; R++) {
+        for (int i = 0; i < 3 * UINT16_RANGE; i++) {
+            stats.sequenceNumberReceived(seq);
+            seq = seq + (quint16)1;
+
+            assert(stats.getNumDuplicate() == 0);
+            assert(stats.getNumEarly() == 0);
+            assert(stats.getNumLate() == 0);
+            assert(stats.getNumLost() == 0);
+            assert(stats.getNumReceived() == i + 1);
+            assert(stats.getNumRecovered() == 0);
+        }
+        stats.reset();
+    }
+}
+
+void SequenceNumberStatsTests::earlyLateTest() {
+
+    SequenceNumberStats stats;
+    quint16 seq = 65530;
+    int numSent = 0;
+
+    int numEarly = 0;
+    int numLate = 0;
+    int numLost = 0;
+    int numRecovered = 0;
+
+    for (int R = 0; R < 2; R++) {
+        for (int T = 0; T < 10000; T++) {
+
+            // insert 7 consecutive
+            for (int i = 0; i < 7; i++) {
+                stats.sequenceNumberReceived(seq);
+                seq = seq + (quint16)1;
+                numSent++;
+
+                assert(stats.getNumDuplicate() == 0);
+                assert(stats.getNumEarly() == numEarly);
+                assert(stats.getNumLate() == numLate);
+                assert(stats.getNumLost() == numLost);
+                assert(stats.getNumReceived() == numSent);
+                assert(stats.getNumRecovered() == numRecovered);
+            }
+
+            // skip 10
+            quint16 skipped = seq;
+            seq = seq + (quint16)10;
+
+            // insert 36 consecutive
+            numEarly++;
+            numLost += 10;
+            for (int i = 0; i < 36; i++) {
+                stats.sequenceNumberReceived(seq);
+                seq = seq + (quint16)1;
+                numSent++;
+
+                assert(stats.getNumDuplicate() == 0);
+                assert(stats.getNumEarly() == numEarly);
+                assert(stats.getNumLate() == numLate);
+                assert(stats.getNumLost() == numLost);
+                assert(stats.getNumReceived() == numSent);
+                assert(stats.getNumRecovered() == numRecovered);
+            }
+
+            // send ones we skipped
+            for (int i = 0; i < 10; i++) {
+                stats.sequenceNumberReceived(skipped);
+                skipped = skipped + (quint16)1;
+                numSent++;
+                numLate++;
+                numLost--;
+                numRecovered++;
+
+                assert(stats.getNumDuplicate() == 0);
+                assert(stats.getNumEarly() == numEarly);
+                assert(stats.getNumLate() == numLate);
+                assert(stats.getNumLost() == numLost);
+                assert(stats.getNumReceived() == numSent);
+                assert(stats.getNumRecovered() == numRecovered);
+            }
+        }
+        stats.reset();
+    }
+}
+
+void SequenceNumberStatsTests::duplicateTest() {
+
+    SequenceNumberStats stats;
+    quint16 seq = 12345;
+    int numSent = 0;
+
+    int numDuplicate = 0;
+    int numEarly = 0;
+    int numLate = 0;
+    int numLost = 0;
+
+    for (int R = 0; R < 2; R++) {
+        for (int T = 0; T < 10000; T++) {
+
+            quint16 duplicate = seq;
+
+            // insert 7 consecutive
+            for (int i = 0; i < 7; i++) {
+                stats.sequenceNumberReceived(seq);
+                seq = seq + (quint16)1;
+                numSent++;
+
+                assert(stats.getNumDuplicate() == numDuplicate);
+                assert(stats.getNumEarly() == numEarly);
+                assert(stats.getNumLate() == numLate);
+                assert(stats.getNumLost() == numLost);
+                assert(stats.getNumReceived() == numSent);
+                assert(stats.getNumRecovered() == 0);
+            }
+
+            // skip 10
+            seq = seq + (quint16)10;
+
+
+            quint16 duplicate2 = seq;
+
+            numEarly++;
+            numLost += 10;
+            // insert 36 consecutive
+            for (int i = 0; i < 36; i++) {
+                stats.sequenceNumberReceived(seq);
+                seq = seq + (quint16)1;
+                numSent++;
+
+                assert(stats.getNumDuplicate() == numDuplicate);
+                assert(stats.getNumEarly() == numEarly);
+                assert(stats.getNumLate() == numLate);
+                assert(stats.getNumLost() == numLost);
+                assert(stats.getNumReceived() == numSent);
+                assert(stats.getNumRecovered() == 0);
+            }
+
+            // send 5 duplicates from before skip
+            for (int i = 0; i < 5; i++) {
+                stats.sequenceNumberReceived(duplicate);
+                duplicate = duplicate + (quint16)1;
+                numSent++;
+                numDuplicate++;
+                numLate++;
+
+                assert(stats.getNumDuplicate() == numDuplicate);
+                assert(stats.getNumEarly() == numEarly);
+                assert(stats.getNumLate() == numLate);
+                assert(stats.getNumLost() == numLost);
+                assert(stats.getNumReceived() == numSent);
+                assert(stats.getNumRecovered() == 0);
+            }
+
+            // send 5 duplicates from after skip
+            for (int i = 0; i < 5; i++) {
+                stats.sequenceNumberReceived(duplicate2);
+                duplicate2 = duplicate2 + (quint16)1;
+                numSent++;
+                numDuplicate++;
+                numLate++;
+
+                assert(stats.getNumDuplicate() == numDuplicate);
+                assert(stats.getNumEarly() == numEarly);
+                assert(stats.getNumLate() == numLate);
+                assert(stats.getNumLost() == numLost);
+                assert(stats.getNumReceived() == numSent);
+                assert(stats.getNumRecovered() == 0);
+            }
+        }
+        stats.reset();
+    }
+}
+
+void SequenceNumberStatsTests::pruneTest() {
+    
+    SequenceNumberStats stats;
+    quint16 seq = 54321;
+    int numSent = 0;
+
+    int numEarly = 0;
+    int numLost = 0;
+
+    for (int R = 0; R < 2; R++) {
+        for (int T = 0; T < 1000; T++) {
+            // insert 1 seq
+            stats.sequenceNumberReceived(seq);
+            seq = seq + (quint16)1;
+            numSent++;
+
+            // skip 1000 seq
+            seq = seq + (quint16)1000;
+            quint16 highestSkipped = seq - (quint16)1;
+
+            // insert 1 seq
+            stats.sequenceNumberReceived(seq);
+            seq = seq + (quint16)1;
+            numSent++;
+            numEarly++;
+            numLost += 1000;
+
+            // skip 10 seq
+            seq = seq + (quint16)10;
+            quint16 highestSkipped2 = seq - (quint16)1;
+
+            // insert 1 seq
+            // insert 1 seq
+            stats.sequenceNumberReceived(seq);
+            seq = seq + (quint16)1;
+            numSent++;
+            numEarly++;
+            numLost += 10;
+
+            const QSet<quint16>& missingSet = stats.getMissingSet();
+            assert(missingSet.size() <= 1000);
+
+            for (int i = 0; i < 10; i++) {
+                assert(missingSet.contains(highestSkipped2));
+                highestSkipped2 = highestSkipped2 - (quint16)1;
+            }
+
+            for (int i = 0; i < 989; i++) {
+                assert(missingSet.contains(highestSkipped));
+                highestSkipped = highestSkipped - (quint16)1;
+            }
+            for (int i = 0; i < 11; i++) {
+                assert(!missingSet.contains(highestSkipped));
+                highestSkipped = highestSkipped - (quint16)1;
+            }
+        }
+        stats.reset();
+    }
+}
diff --git a/tests/networking/src/SequenceNumberStatsTests.h b/tests/networking/src/SequenceNumberStatsTests.h
new file mode 100644
index 0000000000..53a0b66480
--- /dev/null
+++ b/tests/networking/src/SequenceNumberStatsTests.h
@@ -0,0 +1,28 @@
+//
+//  AudioRingBufferTests.h
+//  tests/networking/src
+//
+//  Created by Yixin Wang on 6/24/2014
+//  Copyright 2014 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
+//
+
+#ifndef hifi_SequenceNumberStatsTests_h
+#define hifi_SequenceNumberStatsTests_h
+
+#include "SequenceNumberStatsTests.h"
+#include "SequenceNumberStats.h"
+
+namespace SequenceNumberStatsTests {
+
+    void runAllTests();
+
+    void rolloverTest();
+    void earlyLateTest();
+    void duplicateTest();
+    void pruneTest();
+};
+
+#endif // hifi_SequenceNumberStatsTests_h
diff --git a/tests/networking/src/main.cpp b/tests/networking/src/main.cpp
new file mode 100644
index 0000000000..91a59a0e41
--- /dev/null
+++ b/tests/networking/src/main.cpp
@@ -0,0 +1,19 @@
+//
+//  main.cpp
+//  tests/networking/src
+//
+//  Copyright 2014 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
+//
+
+#include "SequenceNumberStatsTests.h"
+#include <stdio.h>
+
+int main(int argc, char** argv) {
+    SequenceNumberStatsTests::runAllTests();
+    printf("tests passed! press enter to exit");
+    getchar();
+    return 0;
+}
diff --git a/tests/octree/CMakeLists.txt b/tests/octree/CMakeLists.txt
index cbdfd02054..9c5e031d74 100644
--- a/tests/octree/CMakeLists.txt
+++ b/tests/octree/CMakeLists.txt
@@ -12,9 +12,9 @@ set(MACRO_DIR ${ROOT_DIR}/cmake/macros)
 # setup for find modules
 set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/")
 
-#find_package(Qt5Network REQUIRED)
-#find_package(Qt5Script REQUIRED)
-#find_package(Qt5Widgets REQUIRED)
+find_package(Qt5Network REQUIRED)
+find_package(Qt5Script REQUIRED)
+find_package(Qt5Widgets REQUIRED)
 
 include(${MACRO_DIR}/SetupHifiProject.cmake)
 setup_hifi_project(${TARGET_NAME} TRUE)
@@ -22,7 +22,7 @@ setup_hifi_project(${TARGET_NAME} TRUE)
 include(${MACRO_DIR}/AutoMTC.cmake)
 auto_mtc(${TARGET_NAME} ${ROOT_DIR})
 
-#qt5_use_modules(${TARGET_NAME} Network Script Widgets)
+qt5_use_modules(${TARGET_NAME} Network Script Widgets)
 
 #include glm
 include(${MACRO_DIR}/IncludeGLM.cmake)
@@ -30,10 +30,20 @@ include_glm(${TARGET_NAME} ${ROOT_DIR})
 
 # link in the shared libraries
 include(${MACRO_DIR}/LinkHifiLibrary.cmake)
-link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR})
+link_hifi_library(models ${TARGET_NAME} ${ROOT_DIR})
 link_hifi_library(octree ${TARGET_NAME} ${ROOT_DIR})
+link_hifi_library(audio ${TARGET_NAME} ${ROOT_DIR})
+link_hifi_library(networking ${TARGET_NAME} ${ROOT_DIR})
+link_hifi_library(animation ${TARGET_NAME} ${ROOT_DIR})
+link_hifi_library(fbx ${TARGET_NAME} ${ROOT_DIR})
+link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR})
 
 IF (WIN32)
+    # add a definition for ssize_t so that windows doesn't bail
+    add_definitions(-Dssize_t=long)
+
     #target_link_libraries(${TARGET_NAME} Winmm Ws2_32)
+    target_link_libraries(${TARGET_NAME} wsock32.lib)
 ENDIF(WIN32)
 
+
diff --git a/tests/octree/src/ModelTests.cpp b/tests/octree/src/ModelTests.cpp
new file mode 100644
index 0000000000..2cca4b43f6
--- /dev/null
+++ b/tests/octree/src/ModelTests.cpp
@@ -0,0 +1,264 @@
+//
+//  ModelTests.h
+//  tests/octree/src
+//
+//  Created by Brad Hefta-Gaub on 06/04/2014.
+//  Copyright 2014 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
+//
+//  TODO:
+//    * need to add expected results and accumulation of test success/failure
+//
+
+#include <QDebug>
+
+#include <Octree.h>
+#include <ModelItem.h>
+#include <ModelTree.h>
+#include <ModelTreeElement.h>
+#include <OctreeConstants.h>
+#include <PropertyFlags.h>
+#include <SharedUtil.h>
+
+#include "ModelTests.h"
+
+void ModelTests::modelTreeTests(bool verbose) {
+    int testsTaken = 0;
+    int testsPassed = 0;
+    int testsFailed = 0;
+
+    if (verbose) {
+        qDebug() << "******************************************************************************************";
+    }
+    
+    qDebug() << "ModelTests::modelTreeTests()";
+
+    // Tree, id, and model properties used in many tests below...
+    ModelTree tree;
+    uint32_t id = 1;
+    ModelItemID modelID(id);
+    modelID.isKnownID = false; // this is a temporary workaround to allow local tree models to be added with known IDs
+    ModelItemProperties properties;
+    float oneMeter = 1.0f;
+    float halfMeter = oneMeter / 2.0f;
+    float halfOfDomain = TREE_SCALE * 0.5f;
+    glm::vec3 positionNearOriginInMeters(oneMeter, oneMeter, oneMeter); // when using properties, these are in meter not tree units
+    glm::vec3 positionAtCenterInMeters(halfOfDomain, halfOfDomain, halfOfDomain);
+    glm::vec3 positionNearOriginInTreeUnits = positionNearOriginInMeters / (float)TREE_SCALE;
+    glm::vec3 positionAtCenterInTreeUnits = positionAtCenterInMeters / (float)TREE_SCALE;
+
+    {
+        testsTaken++;
+        QString testName = "add model to tree and search";
+        if (verbose) {
+            qDebug() << "Test" << testsTaken <<":" << qPrintable(testName);
+        }
+        
+        properties.setPosition(positionAtCenterInMeters);
+        properties.setRadius(halfMeter);
+        properties.setModelURL("https://s3-us-west-1.amazonaws.com/highfidelity-public/ozan/theater.fbx");
+
+        tree.addModel(modelID, properties);
+        
+        float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units
+        const ModelItem* foundModelByRadius = tree.findClosestModel(positionAtCenterInTreeUnits, targetRadius);
+        const ModelItem* foundModelByID = tree.findModelByID(id);
+        
+        if (verbose) {
+            qDebug() << "foundModelByRadius=" << foundModelByRadius;
+            qDebug() << "foundModelByID=" << foundModelByID;
+        }
+
+        bool passed = foundModelByRadius && foundModelByID && (foundModelByRadius == foundModelByID);
+        if (passed) {
+            testsPassed++;
+        } else {
+            testsFailed++;
+            qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName);
+        }
+    }
+
+    modelID.isKnownID = true; // this is a temporary workaround to allow local tree models to be added with known IDs
+
+    {
+        testsTaken++;
+        QString testName = "change position of model in tree";
+        if (verbose) {
+            qDebug() << "Test" << testsTaken <<":" << qPrintable(testName);
+        }
+        
+        glm::vec3 newPosition = positionNearOriginInMeters;
+
+        properties.setPosition(newPosition);
+
+        tree.updateModel(modelID, properties);
+        
+        float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units
+        const ModelItem* foundModelByRadius = tree.findClosestModel(positionNearOriginInTreeUnits, targetRadius);
+        const ModelItem* foundModelByID = tree.findModelByID(id);
+        
+        if (verbose) {
+            qDebug() << "foundModelByRadius=" << foundModelByRadius;
+            qDebug() << "foundModelByID=" << foundModelByID;
+        }
+
+        // NOTE: This test is currently expected to fail in the production code. There's a bug in ModelTree::updateModel()
+        // that does not update the actual location of the model into the correct element when modified locally. So this
+        // test will fail. There's a new optimized and correctly working version of updateModel() that fixes this problem.
+        bool passed = foundModelByRadius && foundModelByID && (foundModelByRadius == foundModelByID);
+        if (passed) {
+            testsPassed++;
+            qDebug() << "NOTE: Expected to FAIL - Test" << testsTaken <<":" << qPrintable(testName);
+        } else {
+            testsFailed++;
+            qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName);
+            qDebug() << "NOTE: Expected to FAIL - Test" << testsTaken <<":" << qPrintable(testName);
+        }
+    }
+
+    {
+        testsTaken++;
+        QString testName = "change position of model in tree back to center";
+        if (verbose) {
+            qDebug() << "Test" << testsTaken <<":" << qPrintable(testName);
+        }
+        
+        glm::vec3 newPosition = positionAtCenterInMeters;
+
+        properties.setPosition(newPosition);
+
+        tree.updateModel(modelID, properties);
+        
+        float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units
+        const ModelItem* foundModelByRadius = tree.findClosestModel(positionAtCenterInTreeUnits, targetRadius);
+        const ModelItem* foundModelByID = tree.findModelByID(id);
+        
+        if (verbose) {
+            qDebug() << "foundModelByRadius=" << foundModelByRadius;
+            qDebug() << "foundModelByID=" << foundModelByID;
+        }
+
+        bool passed = foundModelByRadius && foundModelByID && (foundModelByRadius == foundModelByID);
+        if (passed) {
+            testsPassed++;
+        } else {
+            testsFailed++;
+            qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName);
+        }
+    }
+
+    {
+        testsTaken++;
+        QString testName = "Performance - findClosestModel() 1,000,000 times";
+        if (verbose) {
+            qDebug() << "Test" << testsTaken <<":" << qPrintable(testName);
+        }
+
+        float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units
+        const int TEST_ITERATIONS = 1000000;
+        quint64 start = usecTimestampNow();
+        const ModelItem* foundModelByRadius = NULL;
+        for (int i = 0; i < TEST_ITERATIONS; i++) {        
+            foundModelByRadius = tree.findClosestModel(positionAtCenterInTreeUnits, targetRadius);
+        }
+        quint64 end = usecTimestampNow();
+        
+        if (verbose) {
+            qDebug() << "foundModelByRadius=" << foundModelByRadius;
+        }
+
+        bool passed = foundModelByRadius;
+        if (passed) {
+            testsPassed++;
+        } else {
+            testsFailed++;
+            qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName);
+        }
+        float USECS_PER_MSECS = 1000.0f;
+        float elapsedInMSecs = (float)(end - start) / USECS_PER_MSECS;
+        qDebug() << "TIME - Test" << testsTaken <<":" << qPrintable(testName) << "elapsed=" << elapsedInMSecs << "msecs";
+    }
+
+    {
+        testsTaken++;
+        QString testName = "Performance - findModelByID() 1,000,000 times";
+        if (verbose) {
+            qDebug() << "Test" << testsTaken <<":" << qPrintable(testName);
+        }
+
+        const int TEST_ITERATIONS = 1000000;
+        quint64 start = usecTimestampNow();
+        const ModelItem* foundModelByID = NULL;
+        for (int i = 0; i < TEST_ITERATIONS; i++) {        
+            foundModelByID = tree.findModelByID(id);
+        }
+        quint64 end = usecTimestampNow();
+        
+        if (verbose) {
+            qDebug() << "foundModelByID=" << foundModelByID;
+        }
+
+        bool passed = foundModelByID;
+        if (passed) {
+            testsPassed++;
+        } else {
+            testsFailed++;
+            qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName);
+        }
+        float USECS_PER_MSECS = 1000.0f;
+        float elapsedInMSecs = (float)(end - start) / USECS_PER_MSECS;
+        qDebug() << "TIME - Test" << testsTaken <<":" << qPrintable(testName) << "elapsed=" << elapsedInMSecs << "msecs";
+    }
+
+    {
+        testsTaken++;
+        QString testName = "Performance - add model to tree 10,000 times";
+        if (verbose) {
+            qDebug() << "Test" << testsTaken <<":" << qPrintable(testName);
+        }
+
+        const int TEST_ITERATIONS = 10000;
+        quint64 start = usecTimestampNow();
+        for (int i = 0; i < TEST_ITERATIONS; i++) {        
+            uint32_t id = i + 2; // make sure it doesn't collide with previous model ids
+            ModelItemID modelID(id);
+            modelID.isKnownID = false; // this is a temporary workaround to allow local tree models to be added with known IDs
+
+            float randomX = randFloatInRange(0.0f ,(float)TREE_SCALE);
+            float randomY = randFloatInRange(0.0f ,(float)TREE_SCALE);
+            float randomZ = randFloatInRange(0.0f ,(float)TREE_SCALE);
+            glm::vec3 randomPositionInMeters(randomX,randomY,randomZ);
+
+            properties.setPosition(randomPositionInMeters);
+            properties.setRadius(halfMeter);
+            properties.setModelURL("https://s3-us-west-1.amazonaws.com/highfidelity-public/ozan/theater.fbx");
+
+            tree.addModel(modelID, properties);
+        }
+        quint64 end = usecTimestampNow();
+        
+        bool passed = true;
+        if (passed) {
+            testsPassed++;
+        } else {
+            testsFailed++;
+            qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName);
+        }
+        float USECS_PER_MSECS = 1000.0f;
+        float elapsedInMSecs = (float)(end - start) / USECS_PER_MSECS;
+        qDebug() << "TIME - Test" << testsTaken <<":" << qPrintable(testName) << "elapsed=" << elapsedInMSecs << "msecs";
+    }
+
+    qDebug() << "   tests passed:" << testsPassed << "out of" << testsTaken;
+    if (verbose) {
+        qDebug() << "******************************************************************************************";
+    }
+}
+
+
+void ModelTests::runAllTests(bool verbose) {
+    modelTreeTests(verbose);
+}
+
diff --git a/tests/octree/src/ModelTests.h b/tests/octree/src/ModelTests.h
new file mode 100644
index 0000000000..dd764edf9d
--- /dev/null
+++ b/tests/octree/src/ModelTests.h
@@ -0,0 +1,20 @@
+//
+//  ModelTests.h
+//  tests/octree/src
+//
+//  Created by Brad Hefta-Gaub on 06/04/2014.
+//  Copyright 2014 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
+//
+
+#ifndef hifi_ModelTests_h
+#define hifi_ModelTests_h
+
+namespace ModelTests {
+    void modelTreeTests(bool verbose = false);
+    void runAllTests(bool verbose = false);
+}
+
+#endif // hifi_ModelTests_h
diff --git a/tests/octree/src/main.cpp b/tests/octree/src/main.cpp
index de7b3926ae..590df268c3 100644
--- a/tests/octree/src/main.cpp
+++ b/tests/octree/src/main.cpp
@@ -8,11 +8,13 @@
 //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
 //
 
+#include "ModelTests.h"
 #include "OctreeTests.h"
 #include "AABoxCubeTests.h"
 
 int main(int argc, char** argv) {
     OctreeTests::runAllTests();
     AABoxCubeTests::runAllTests();
+    ModelTests::runAllTests(true);
     return 0;
 }
diff --git a/tests/physics/src/ShapeColliderTests.cpp b/tests/physics/src/ShapeColliderTests.cpp
index 608e012998..bde29ea588 100644
--- a/tests/physics/src/ShapeColliderTests.cpp
+++ b/tests/physics/src/ShapeColliderTests.cpp
@@ -123,8 +123,8 @@ void ShapeColliderTests::sphereTouchesSphere() {
         }
     
         // contactPoint is on surface of sphereA
-        glm::vec3 AtoB = sphereB.getPosition() - sphereA.getPosition();
-        glm::vec3 expectedContactPoint = sphereA.getPosition() + radiusA * glm::normalize(AtoB);
+        glm::vec3 AtoB = sphereB.getTranslation() - sphereA.getTranslation();
+        glm::vec3 expectedContactPoint = sphereA.getTranslation() + radiusA * glm::normalize(AtoB);
         inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
         if (fabs(inaccuracy) > EPSILON) {
             std::cout << __FILE__ << ":" << __LINE__
@@ -153,8 +153,8 @@ void ShapeColliderTests::sphereTouchesSphere() {
         }
     
         // contactPoint is on surface of sphereA
-        glm::vec3 BtoA = sphereA.getPosition() - sphereB.getPosition();
-        glm::vec3 expectedContactPoint = sphereB.getPosition() + radiusB * glm::normalize(BtoA);
+        glm::vec3 BtoA = sphereA.getTranslation() - sphereB.getTranslation();
+        glm::vec3 expectedContactPoint = sphereB.getTranslation() + radiusB * glm::normalize(BtoA);
         inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
         if (fabs(inaccuracy) > EPSILON) {
             std::cout << __FILE__ << ":" << __LINE__
@@ -182,7 +182,7 @@ void ShapeColliderTests::sphereMissesCapsule() {
     glm::quat rotation = glm::angleAxis(angle, axis);
     glm::vec3 translation(15.1f, -27.1f, -38.6f);
     capsuleB.setRotation(rotation);
-    capsuleB.setPosition(translation);
+    capsuleB.setTranslation(translation);
 
     CollisionList collisions(16);
 
@@ -193,7 +193,7 @@ void ShapeColliderTests::sphereMissesCapsule() {
     for (int i = 0; i < numberOfSteps; ++i) {
         // translate sphereA into world-frame
         glm::vec3 localPosition = localStartPosition + ((float)i * delta) * yAxis;
-        sphereA.setPosition(rotation * localPosition + translation);
+        sphereA.setTranslation(rotation * localPosition + translation);
 
         // sphereA agains capsuleB
         if (ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
@@ -236,7 +236,7 @@ void ShapeColliderTests::sphereTouchesCapsule() {
     int numCollisions = 0;
 
     {   // sphereA collides with capsuleB's cylindrical wall
-        sphereA.setPosition(radialOffset * xAxis);
+        sphereA.setTranslation(radialOffset * xAxis);
 
         if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
         {
@@ -258,7 +258,7 @@ void ShapeColliderTests::sphereTouchesCapsule() {
         }
     
         // contactPoint is on surface of sphereA
-        glm::vec3 expectedContactPoint = sphereA.getPosition() - radiusA * xAxis;
+        glm::vec3 expectedContactPoint = sphereA.getTranslation() - radiusA * xAxis;
         inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
         if (fabs(inaccuracy) > EPSILON) {
             std::cout << __FILE__ << ":" << __LINE__
@@ -287,8 +287,8 @@ void ShapeColliderTests::sphereTouchesCapsule() {
         }
     
         // contactPoint is on surface of capsuleB
-        glm::vec3 BtoA = sphereA.getPosition() - capsuleB.getPosition();
-        glm::vec3 closestApproach = capsuleB.getPosition() + glm::dot(BtoA, yAxis) * yAxis;
+        glm::vec3 BtoA = sphereA.getTranslation() - capsuleB.getTranslation();
+        glm::vec3 closestApproach = capsuleB.getTranslation() + glm::dot(BtoA, yAxis) * yAxis;
         expectedContactPoint = closestApproach + radiusB * glm::normalize(BtoA - closestApproach);
         inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
         if (fabs(inaccuracy) > EPSILON) {
@@ -299,7 +299,7 @@ void ShapeColliderTests::sphereTouchesCapsule() {
     }
     {   // sphereA hits end cap at axis
         glm::vec3 axialOffset = (halfHeightB + alpha * radiusA + beta * radiusB) * yAxis;
-        sphereA.setPosition(axialOffset * yAxis);
+        sphereA.setTranslation(axialOffset * yAxis);
         
         if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
         {
@@ -321,7 +321,7 @@ void ShapeColliderTests::sphereTouchesCapsule() {
         }
     
         // contactPoint is on surface of sphereA
-        glm::vec3 expectedContactPoint = sphereA.getPosition() - radiusA * yAxis;
+        glm::vec3 expectedContactPoint = sphereA.getTranslation() - radiusA * yAxis;
         inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
         if (fabs(inaccuracy) > EPSILON) {
             std::cout << __FILE__ << ":" << __LINE__
@@ -362,7 +362,7 @@ void ShapeColliderTests::sphereTouchesCapsule() {
     }
     {   // sphereA hits start cap at axis
         glm::vec3 axialOffset = - (halfHeightB + alpha * radiusA + beta * radiusB) * yAxis;
-        sphereA.setPosition(axialOffset * yAxis);
+        sphereA.setTranslation(axialOffset * yAxis);
         
         if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
         {
@@ -384,7 +384,7 @@ void ShapeColliderTests::sphereTouchesCapsule() {
         }
     
         // contactPoint is on surface of sphereA
-        glm::vec3 expectedContactPoint = sphereA.getPosition() + radiusA * yAxis;
+        glm::vec3 expectedContactPoint = sphereA.getTranslation() + radiusA * yAxis;
         inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
         if (fabs(inaccuracy) > EPSILON) {
             std::cout << __FILE__ << ":" << __LINE__
@@ -441,12 +441,12 @@ void ShapeColliderTests::capsuleMissesCapsule() {
     float totalHalfLength = totalRadius + halfHeightA + halfHeightB;
 
     CapsuleShape capsuleA(radiusA, halfHeightA);
-    CapsuleShape capsuleB(radiusA, halfHeightA);
+    CapsuleShape capsuleB(radiusB, halfHeightB);
 
     CollisionList collisions(16);
 
     // side by side
-    capsuleB.setPosition((1.01f * totalRadius) * xAxis);
+    capsuleB.setTranslation((1.01f * totalRadius) * xAxis);
     if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
     {
         std::cout << __FILE__ << ":" << __LINE__
@@ -461,7 +461,7 @@ void ShapeColliderTests::capsuleMissesCapsule() {
     }
 
     // end to end
-    capsuleB.setPosition((1.01f * totalHalfLength) * xAxis);
+    capsuleB.setTranslation((1.01f * totalHalfLength) * xAxis);
     if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
     {
         std::cout << __FILE__ << ":" << __LINE__
@@ -478,7 +478,7 @@ void ShapeColliderTests::capsuleMissesCapsule() {
     // rotate B and move it to the side
     glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
     capsuleB.setRotation(rotation);
-    capsuleB.setPosition((1.01f * (totalRadius + capsuleB.getHalfHeight())) * xAxis);
+    capsuleB.setTranslation((1.01f * (totalRadius + capsuleB.getHalfHeight())) * xAxis);
     if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
     {
         std::cout << __FILE__ << ":" << __LINE__
@@ -516,7 +516,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
     int numCollisions = 0;
 
     { // side by side
-        capsuleB.setPosition((0.99f * totalRadius) * xAxis);
+        capsuleB.setTranslation((0.99f * totalRadius) * xAxis);
         if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
         {
             std::cout << __FILE__ << ":" << __LINE__
@@ -536,7 +536,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
     }
 
     { // end to end
-        capsuleB.setPosition((0.99f * totalHalfLength) * yAxis);
+        capsuleB.setTranslation((0.99f * totalHalfLength) * yAxis);
 
         if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
         {
@@ -559,7 +559,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
     { // rotate B and move it to the side
         glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
         capsuleB.setRotation(rotation);
-        capsuleB.setPosition((0.99f * (totalRadius + capsuleB.getHalfHeight())) * xAxis);
+        capsuleB.setTranslation((0.99f * (totalRadius + capsuleB.getHalfHeight())) * xAxis);
 
         if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
         {
@@ -584,7 +584,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
         glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
         capsuleB.setRotation(rotation);
         glm::vec3 positionB = ((totalRadius + capsuleB.getHalfHeight()) - overlap) * xAxis;
-        capsuleB.setPosition(positionB);
+        capsuleB.setTranslation(positionB);
 
         // capsuleA vs capsuleB
         if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
@@ -605,7 +605,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
                 << " actual = " << collision->_penetration;
         }
     
-        glm::vec3 expectedContactPoint = capsuleA.getPosition() + radiusA * xAxis;
+        glm::vec3 expectedContactPoint = capsuleA.getTranslation() + radiusA * xAxis;
         inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
         if (fabs(inaccuracy) > EPSILON) {
             std::cout << __FILE__ << ":" << __LINE__
@@ -633,7 +633,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
                 << std::endl;
         }
     
-        expectedContactPoint = capsuleB.getPosition() - (radiusB + halfHeightB) * xAxis;
+        expectedContactPoint = capsuleB.getTranslation() - (radiusB + halfHeightB) * xAxis;
         inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
         if (fabs(inaccuracy) > EPSILON) {
             std::cout << __FILE__ << ":" << __LINE__
@@ -649,7 +649,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
         glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
         capsuleB.setRotation(rotation);
         glm::vec3 positionB = (totalRadius - overlap) * zAxis + shift * yAxis;
-        capsuleB.setPosition(positionB);
+        capsuleB.setTranslation(positionB);
 
         // capsuleA vs capsuleB
         if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
@@ -671,7 +671,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() {
                 << std::endl;
         }
     
-        glm::vec3 expectedContactPoint = capsuleA.getPosition() + radiusA * zAxis + shift * yAxis;
+        glm::vec3 expectedContactPoint = capsuleA.getTranslation() + radiusA * zAxis + shift * yAxis;
         inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
         if (fabs(inaccuracy) > EPSILON) {
             std::cout << __FILE__ << ":" << __LINE__
@@ -708,7 +708,7 @@ void ShapeColliderTests::sphereTouchesAACubeFaces() {
             float overlap = 0.25f;
             float sphereOffset = 0.5f * cubeSide + sphereRadius - overlap;
             sphereCenter = cubeCenter + sphereOffset * axis;
-            sphere.setPosition(sphereCenter);
+            sphere.setTranslation(sphereCenter);
     
             if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
                 std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube.  axis = " << axis << std::endl;
@@ -741,7 +741,7 @@ void ShapeColliderTests::sphereTouchesAACubeFaces() {
             float overlap = 1.25f * sphereRadius;
             float sphereOffset = 0.5f * cubeSide + sphereRadius - overlap;
             sphereCenter = cubeCenter + sphereOffset * axis;
-            sphere.setPosition(sphereCenter);
+            sphere.setTranslation(sphereCenter);
     
             if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
                 std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube." 
@@ -815,7 +815,7 @@ void ShapeColliderTests::sphereTouchesAACubeEdges() {
         float overlap = 0.25f;
     
         sphereCenter = cubeCenter + (lengthAxis * 0.5f * cubeSide + sphereRadius - overlap) * axis;
-        sphere.setPosition(sphereCenter);
+        sphere.setTranslation(sphereCenter);
     
         if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
             std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube.  axis = " << axis << std::endl;
@@ -857,42 +857,42 @@ void ShapeColliderTests::sphereMissesAACube() {
 
     // top
     sphereCenter = cubeCenter + sphereOffset * yAxis;
-    sphere.setPosition(sphereCenter);
+    sphere.setTranslation(sphereCenter);
     if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
         std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl;
     }
     
     // bottom
     sphereCenter = cubeCenter - sphereOffset * yAxis;
-    sphere.setPosition(sphereCenter);
+    sphere.setTranslation(sphereCenter);
     if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
         std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl;
     }
 
     // left
     sphereCenter = cubeCenter + sphereOffset * xAxis;
-    sphere.setPosition(sphereCenter);
+    sphere.setTranslation(sphereCenter);
     if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
         std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl;
     }
 
     // right
     sphereCenter = cubeCenter - sphereOffset * xAxis;
-    sphere.setPosition(sphereCenter);
+    sphere.setTranslation(sphereCenter);
     if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
         std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl;
     }
 
     // forward
     sphereCenter = cubeCenter + sphereOffset * zAxis;
-    sphere.setPosition(sphereCenter);
+    sphere.setTranslation(sphereCenter);
     if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
         std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl;
     }
 
     // back
     sphereCenter = cubeCenter - sphereOffset * zAxis;
-    sphere.setPosition(sphereCenter);
+    sphere.setTranslation(sphereCenter);
     if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){
         std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl;
     }
@@ -955,7 +955,7 @@ void ShapeColliderTests::rayHitsSphere() {
         rayDirection = rotation * unrotatedRayDirection;
 
         sphere.setRadius(radius);
-        sphere.setPosition(rotation * translation);
+        sphere.setTranslation(rotation * translation);
     
         float distance = FLT_MAX;
         if (!sphere.findRayIntersection(rayStart, rayDirection, distance)) {
@@ -994,7 +994,7 @@ void ShapeColliderTests::rayBarelyHitsSphere() {
 
     rayStart = rotation * (rayStart + translation);
     rayDirection = rotation * rayDirection;
-    sphere.setPosition(rotation * translation);
+    sphere.setTranslation(rotation * translation);
 
     // ...and test again
     distance = FLT_MAX;
@@ -1032,7 +1032,7 @@ void ShapeColliderTests::rayBarelyMissesSphere() {
 
     rayStart = rotation * (rayStart + translation);
     rayDirection = rotation * rayDirection;
-    sphere.setPosition(rotation * translation);
+    sphere.setTranslation(rotation * translation);
 
     // ...and test again
     distance = FLT_MAX;
@@ -1183,10 +1183,10 @@ void ShapeColliderTests::rayMissesCapsule() {
 
 void ShapeColliderTests::rayHitsPlane() {
     // make a simple plane
-    float planeDistanceFromOrigin = 3.579;
+    float planeDistanceFromOrigin = 3.579f;
     glm::vec3 planePosition(0.0f, planeDistanceFromOrigin, 0.0f);
     PlaneShape plane;
-    plane.setPosition(planePosition);
+    plane.setTranslation(planePosition);
 
     // make a simple ray
     float startDistance = 1.234f;
@@ -1209,7 +1209,7 @@ void ShapeColliderTests::rayHitsPlane() {
     glm::vec3 axis = glm::normalize( glm::vec3(-7.0f, 2.8f, 9.3f) );
     glm::quat rotation = glm::angleAxis(angle, axis);
 
-    plane.setPosition(rotation * planePosition);
+    plane.setTranslation(rotation * planePosition);
     plane.setRotation(rotation);
     rayStart = rotation * rayStart;
     rayDirection = rotation * rayDirection;
@@ -1228,10 +1228,10 @@ void ShapeColliderTests::rayHitsPlane() {
 
 void ShapeColliderTests::rayMissesPlane() {
     // make a simple plane
-    float planeDistanceFromOrigin = 3.579;
+    float planeDistanceFromOrigin = 3.579f;
     glm::vec3 planePosition(0.0f, planeDistanceFromOrigin, 0.0f);
     PlaneShape plane;
-    plane.setPosition(planePosition);
+    plane.setTranslation(planePosition);
 
     { // parallel rays should miss
         float startDistance = 1.234f;
@@ -1251,7 +1251,7 @@ void ShapeColliderTests::rayMissesPlane() {
         glm::vec3 axis = glm::normalize( glm::vec3(-7.0f, 2.8f, 9.3f) );
         glm::quat rotation = glm::angleAxis(angle, axis);
     
-        plane.setPosition(rotation * planePosition);
+        plane.setTranslation(rotation * planePosition);
         plane.setRotation(rotation);
         rayStart = rotation * rayStart;
         rayDirection = rotation * rayDirection;
@@ -1283,7 +1283,7 @@ void ShapeColliderTests::rayMissesPlane() {
         glm::vec3 axis = glm::normalize( glm::vec3(-7.0f, 2.8f, 9.3f) );
         glm::quat rotation = glm::angleAxis(angle, axis);
     
-        plane.setPosition(rotation * planePosition);
+        plane.setTranslation(rotation * planePosition);
         plane.setRotation(rotation);
         rayStart = rotation * rayStart;
         rayDirection = rotation * rayDirection;
diff --git a/tests/physics/src/VerletShapeTests.cpp b/tests/physics/src/VerletShapeTests.cpp
new file mode 100644
index 0000000000..3a3bd43278
--- /dev/null
+++ b/tests/physics/src/VerletShapeTests.cpp
@@ -0,0 +1,769 @@
+//
+//  VerletShapeTests.cpp
+//  tests/physics/src
+//
+//  Created by Andrew Meadows on 02/21/2014.
+//  Copyright 2014 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
+//
+
+//#include <stdio.h>
+#include <iostream>
+#include <math.h>
+
+#include <glm/glm.hpp>
+#include <glm/gtx/quaternion.hpp>
+
+#include <CollisionInfo.h>
+#include <Ragdoll.h>    // for VerletPoint
+#include <ShapeCollider.h>
+#include <SharedUtil.h>
+#include <VerletCapsuleShape.h>
+#include <VerletSphereShape.h>
+#include <StreamUtils.h>
+
+#include "VerletShapeTests.h"
+
+const glm::vec3 origin(0.0f);
+static const glm::vec3 xAxis(1.0f, 0.0f, 0.0f);
+static const glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
+static const glm::vec3 zAxis(0.0f, 0.0f, 1.0f);
+
+void VerletShapeTests::setSpherePosition() {
+    float radius = 1.0f;
+    glm::vec3 offset(1.23f, 4.56f, 7.89f);
+    VerletPoint point;
+    VerletSphereShape sphere(radius, &point);
+
+    point._position = glm::vec3(0.f);
+    float d = glm::distance(glm::vec3(0.0f), sphere.getTranslation());
+    if (d != 0.0f) {
+        std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should be at origin" << std::endl;
+    }
+
+    point._position = offset;
+    d = glm::distance(glm::vec3(0.0f), sphere.getTranslation());
+    if (d != glm::length(offset)) {
+        std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should be at offset" << std::endl;
+    }
+}
+
+void VerletShapeTests::sphereMissesSphere() {
+    // non-overlapping spheres of unequal size
+
+    float radiusA = 7.0f;
+    float radiusB = 3.0f;
+    float alpha = 1.2f;
+    float beta = 1.3f;
+    glm::vec3 offsetDirection = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f));
+    float offsetDistance = alpha * radiusA + beta * radiusB;
+
+    // create points for the sphere centers
+    VerletPoint points[2];
+
+    // give pointers to the spheres
+    VerletSphereShape sphereA(radiusA, (points + 0));
+    VerletSphereShape sphereB(radiusB, (points + 1));
+
+    // set the positions of the spheres by slamming the points directly
+    points[0]._position = origin;
+    points[1]._position = offsetDistance * offsetDirection;
+
+    CollisionList collisions(16);
+
+    // collide A to B...
+    {
+        bool touching = ShapeCollider::collideShapes(&sphereA, &sphereB, collisions);
+        if (touching) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: sphereA and sphereB should NOT touch" << std::endl;
+        }
+    }
+
+    // collide B to A...
+    {
+        bool touching = ShapeCollider::collideShapes(&sphereB, &sphereA, collisions);
+        if (touching) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: sphereA and sphereB should NOT touch" << std::endl;
+        }
+    }
+
+    // also test shapeShape
+    {
+        bool touching = ShapeCollider::collideShapes(&sphereB, &sphereA, collisions);
+        if (touching) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: sphereA and sphereB should NOT touch" << std::endl;
+        }
+    }
+
+    if (collisions.size() > 0) {
+        std::cout << __FILE__ << ":" << __LINE__
+            << " ERROR: expected empty collision list but size is " << collisions.size()
+            << std::endl;
+    }
+}
+
+void VerletShapeTests::sphereTouchesSphere() {
+    // overlapping spheres of unequal size
+    float radiusA = 7.0f;
+    float radiusB = 3.0f;
+    float alpha = 0.2f;
+    float beta = 0.3f;
+    glm::vec3 offsetDirection = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f));
+    float offsetDistance = alpha * radiusA + beta * radiusB;
+    float expectedPenetrationDistance = (1.0f - alpha) * radiusA + (1.0f - beta) * radiusB;
+    glm::vec3 expectedPenetration = expectedPenetrationDistance * offsetDirection;
+
+    // create two points for the sphere centers
+    VerletPoint points[2];
+
+    // give pointers to the spheres
+    VerletSphereShape sphereA(radiusA, points+0);
+    VerletSphereShape sphereB(radiusB, points+1);
+
+    // set the positions of the spheres by slamming the points directly
+    points[0]._position = origin;
+    points[1]._position = offsetDistance * offsetDirection;
+
+    CollisionList collisions(16);
+    int numCollisions = 0;
+
+    // collide A to B...
+    {
+        bool touching = ShapeCollider::collideShapes(&sphereA, &sphereB, collisions);
+        if (!touching) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: sphereA and sphereB should touch" << std::endl;
+        } else {
+            ++numCollisions;
+        }
+
+        // verify state of collisions
+        if (numCollisions != collisions.size()) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: expected collisions size of " << numCollisions << " but actual size is " << collisions.size()
+                << std::endl;
+        }
+        CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
+        if (!collision) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: null collision" << std::endl;
+        }
+    
+        // penetration points from sphereA into sphereB
+        float inaccuracy = glm::length(collision->_penetration - expectedPenetration);
+        if (fabs(inaccuracy) > EPSILON) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: bad penetration: expected = " << expectedPenetration
+                << " actual = " << collision->_penetration;
+        }
+    
+        // contactPoint is on surface of sphereA
+        glm::vec3 AtoB = sphereB.getTranslation() - sphereA.getTranslation();
+        glm::vec3 expectedContactPoint = sphereA.getTranslation() + radiusA * glm::normalize(AtoB);
+        inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
+        if (fabs(inaccuracy) > EPSILON) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: bad contactPoint: expected = " << expectedContactPoint
+                << " actual = " << collision->_contactPoint;
+        }
+    }
+
+    // collide B to A...
+    {
+        bool touching = ShapeCollider::collideShapes(&sphereB, &sphereA, collisions);
+        if (!touching) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: sphereA and sphereB should touch" << std::endl;
+        } else {
+            ++numCollisions;
+        }
+    
+        // penetration points from sphereA into sphereB
+        CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
+        float inaccuracy = glm::length(collision->_penetration + expectedPenetration);
+        if (fabs(inaccuracy) > EPSILON) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: bad penetration: expected = " << expectedPenetration
+                << " actual = " << collision->_penetration;
+        }
+    
+        // contactPoint is on surface of sphereA
+        glm::vec3 BtoA = sphereA.getTranslation() - sphereB.getTranslation();
+        glm::vec3 expectedContactPoint = sphereB.getTranslation() + radiusB * glm::normalize(BtoA);
+        inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
+        if (fabs(inaccuracy) > EPSILON) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: bad contactPoint: expected = " << expectedContactPoint
+                << " actual = " << collision->_contactPoint;
+        }
+    }
+}
+
+void VerletShapeTests::sphereMissesCapsule() {
+    // non-overlapping sphere and capsule
+    float radiusA = 1.5f;
+    float radiusB = 2.3f;
+    float totalRadius = radiusA + radiusB;
+    float halfHeightB = 1.7f;
+    float axialOffset = totalRadius + 1.1f * halfHeightB;
+    float radialOffset = 1.2f * radiusA + 1.3f * radiusB;
+    
+    // create points for the sphere + capsule
+    VerletPoint points[3];
+    for (int i = 0; i < 3; ++i) {
+        points[i]._position = glm::vec3(0.0f);
+    }
+
+    // give the points to the shapes
+    VerletSphereShape sphereA(radiusA, points);
+    VerletCapsuleShape capsuleB(radiusB, points+1, points+2);
+    capsuleB.setHalfHeight(halfHeightB);
+
+    // give the capsule some arbitrary transform
+    float angle = 37.8f;
+    glm::vec3 axis = glm::normalize( glm::vec3(-7.0f, 2.8f, 9.3f) );
+    glm::quat rotation = glm::angleAxis(angle, axis);
+    glm::vec3 translation(15.1f, -27.1f, -38.6f);
+    capsuleB.setRotation(rotation);
+    capsuleB.setTranslation(translation);
+
+    CollisionList collisions(16);
+
+    // walk sphereA along the local yAxis next to, but not touching, capsuleB
+    glm::vec3 localStartPosition(radialOffset, axialOffset, 0.0f);
+    int numberOfSteps = 10;
+    float delta = 1.3f * (totalRadius + halfHeightB) / (numberOfSteps - 1);
+    for (int i = 0; i < numberOfSteps; ++i) {
+        // translate sphereA into world-frame
+        glm::vec3 localPosition = localStartPosition + ((float)i * delta) * yAxis;
+        sphereA.setTranslation(rotation * localPosition + translation);
+
+        // sphereA agains capsuleB
+        if (ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
+        {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: sphere and capsule should NOT touch"
+                << std::endl;
+        }
+
+        // capsuleB against sphereA
+        if (ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions))
+        {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: sphere and capsule should NOT touch"
+                << std::endl;
+        }
+    }
+
+    if (collisions.size() > 0) {
+        std::cout << __FILE__ << ":" << __LINE__
+            << " ERROR: expected empty collision list but size is " << collisions.size()
+            << std::endl;
+    }
+}
+
+void VerletShapeTests::sphereTouchesCapsule() {
+    // overlapping sphere and capsule
+    float radiusA = 2.0f;
+    float radiusB = 1.0f;
+    float totalRadius = radiusA + radiusB;
+    float halfHeightB = 2.0f;
+    float alpha = 0.5f;
+    float beta = 0.5f;
+    float radialOffset = alpha * radiusA + beta * radiusB;
+    
+    // create points for the sphere + capsule
+    VerletPoint points[3];
+    for (int i = 0; i < 3; ++i) {
+        points[i]._position = glm::vec3(0.0f);
+    }
+
+    // give the points to the shapes
+    VerletSphereShape sphereA(radiusA, points);
+    VerletCapsuleShape capsuleB(radiusB, points+1, points+2);
+    capsuleB.setHalfHeight(halfHeightB);
+
+    CollisionList collisions(16);
+    int numCollisions = 0;
+
+    {   // sphereA collides with capsuleB's cylindrical wall
+        sphereA.setTranslation(radialOffset * xAxis);
+
+        if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
+        {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: sphere and capsule should touch"
+                << std::endl;
+        } else {
+            ++numCollisions;
+        }
+    
+        // penetration points from sphereA into capsuleB
+        CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
+        glm::vec3 expectedPenetration = (radialOffset - totalRadius) * xAxis;
+        float inaccuracy = glm::length(collision->_penetration - expectedPenetration);
+        if (fabs(inaccuracy) > EPSILON) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: bad penetration: expected = " << expectedPenetration
+                << " actual = " << collision->_penetration;
+        }
+    
+        // contactPoint is on surface of sphereA
+        glm::vec3 expectedContactPoint = sphereA.getTranslation() - radiusA * xAxis;
+        inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
+        if (fabs(inaccuracy) > EPSILON) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: bad contactPoint: expected = " << expectedContactPoint
+                << " actual = " << collision->_contactPoint;
+        }
+
+        // capsuleB collides with sphereA
+        if (!ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions))
+        {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: capsule and sphere should touch"
+                << std::endl;
+        } else {
+            ++numCollisions;
+        }
+    
+        // penetration points from sphereA into capsuleB
+        collision = collisions.getCollision(numCollisions - 1);
+        expectedPenetration = - (radialOffset - totalRadius) * xAxis;
+        inaccuracy = glm::length(collision->_penetration - expectedPenetration);
+        if (fabs(inaccuracy) > EPSILON) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: bad penetration: expected = " << expectedPenetration
+                << " actual = " << collision->_penetration;
+        }
+    
+        // contactPoint is on surface of capsuleB
+        glm::vec3 BtoA = sphereA.getTranslation() - capsuleB.getTranslation();
+        glm::vec3 closestApproach = capsuleB.getTranslation() + glm::dot(BtoA, yAxis) * yAxis;
+        expectedContactPoint = closestApproach + radiusB * glm::normalize(BtoA - closestApproach);
+        inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
+        if (fabs(inaccuracy) > EPSILON) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: bad contactPoint: expected = " << expectedContactPoint
+                << " actual = " << collision->_contactPoint;
+        }
+    }
+    {   // sphereA hits end cap at axis
+        glm::vec3 axialOffset = (halfHeightB + alpha * radiusA + beta * radiusB) * yAxis;
+        sphereA.setTranslation(axialOffset * yAxis);
+        
+        if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
+        {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: sphere and capsule should touch"
+                << std::endl;
+        } else {
+            ++numCollisions;
+        }
+    
+        // penetration points from sphereA into capsuleB
+        CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
+        glm::vec3 expectedPenetration = - ((1.0f - alpha) * radiusA + (1.0f - beta) * radiusB) * yAxis;
+        float inaccuracy = glm::length(collision->_penetration - expectedPenetration);
+        if (fabs(inaccuracy) > EPSILON) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: bad penetration: expected = " << expectedPenetration
+                << " actual = " << collision->_penetration;
+        }
+    
+        // contactPoint is on surface of sphereA
+        glm::vec3 expectedContactPoint = sphereA.getTranslation() - radiusA * yAxis;
+        inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
+        if (fabs(inaccuracy) > EPSILON) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: bad contactPoint: expected = " << expectedContactPoint
+                << " actual = " << collision->_contactPoint;
+        }
+
+        // capsuleB collides with sphereA
+        if (!ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions))
+        {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: capsule and sphere should touch"
+                << std::endl;
+        } else {
+            ++numCollisions;
+        }
+    
+        // penetration points from sphereA into capsuleB
+        collision = collisions.getCollision(numCollisions - 1);
+        expectedPenetration = ((1.0f - alpha) * radiusA + (1.0f - beta) * radiusB) * yAxis;
+        inaccuracy = glm::length(collision->_penetration - expectedPenetration);
+        if (fabs(inaccuracy) > EPSILON) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: bad penetration: expected = " << expectedPenetration
+                << " actual = " << collision->_penetration;
+        }
+    
+        // contactPoint is on surface of capsuleB
+        glm::vec3 endPoint;
+        capsuleB.getEndPoint(endPoint);
+        expectedContactPoint = endPoint + radiusB * yAxis;
+        inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
+        if (fabs(inaccuracy) > EPSILON) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: bad contactPoint: expected = " << expectedContactPoint
+                << " actual = " << collision->_contactPoint;
+        }
+    }
+    {   // sphereA hits start cap at axis
+        glm::vec3 axialOffset = - (halfHeightB + alpha * radiusA + beta * radiusB) * yAxis;
+        sphereA.setTranslation(axialOffset * yAxis);
+        
+        if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions))
+        {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: sphere and capsule should touch"
+                << std::endl;
+        } else {
+            ++numCollisions;
+        }
+    
+        // penetration points from sphereA into capsuleB
+        CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
+        glm::vec3 expectedPenetration = ((1.0f - alpha) * radiusA + (1.0f - beta) * radiusB) * yAxis;
+        float inaccuracy = glm::length(collision->_penetration - expectedPenetration);
+        if (fabs(inaccuracy) > EPSILON) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: bad penetration: expected = " << expectedPenetration
+                << " actual = " << collision->_penetration;
+        }
+    
+        // contactPoint is on surface of sphereA
+        glm::vec3 expectedContactPoint = sphereA.getTranslation() + radiusA * yAxis;
+        inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
+        if (fabs(inaccuracy) > EPSILON) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: bad contactPoint: expected = " << expectedContactPoint
+                << " actual = " << collision->_contactPoint;
+        }
+
+        // capsuleB collides with sphereA
+        if (!ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions))
+        {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: capsule and sphere should touch"
+                << std::endl;
+        } else {
+            ++numCollisions;
+        }
+    
+        // penetration points from sphereA into capsuleB
+        collision = collisions.getCollision(numCollisions - 1);
+        expectedPenetration = - ((1.0f - alpha) * radiusA + (1.0f - beta) * radiusB) * yAxis;
+        inaccuracy = glm::length(collision->_penetration - expectedPenetration);
+        if (fabs(inaccuracy) > EPSILON) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: bad penetration: expected = " << expectedPenetration
+                << " actual = " << collision->_penetration;
+        }
+    
+        // contactPoint is on surface of capsuleB
+        glm::vec3 startPoint;
+        capsuleB.getStartPoint(startPoint);
+        expectedContactPoint = startPoint - radiusB * yAxis;
+        inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
+        if (fabs(inaccuracy) > EPSILON) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: bad contactPoint: expected = " << expectedContactPoint
+                << " actual = " << collision->_contactPoint;
+        }
+    }
+    if (collisions.size() != numCollisions) {
+        std::cout << __FILE__ << ":" << __LINE__
+            << " ERROR: expected " << numCollisions << " collisions but actual number is " << collisions.size()
+            << std::endl;
+    }
+}
+
+void VerletShapeTests::capsuleMissesCapsule() {
+    // non-overlapping capsules
+    float radiusA = 2.0f;
+    float halfHeightA = 3.0f;
+    float radiusB = 3.0f;
+    float halfHeightB = 4.0f;
+
+    float totalRadius = radiusA + radiusB;
+    float totalHalfLength = totalRadius + halfHeightA + halfHeightB;
+
+    // create points for the shapes
+    VerletPoint points[4];
+    for (int i = 0; i < 4; ++i) {
+        points[i]._position = glm::vec3(0.0f);
+    }
+
+    // give the points to the shapes
+    VerletCapsuleShape capsuleA(radiusA, points+0, points+1);
+    VerletCapsuleShape capsuleB(radiusB, points+2, points+3);
+    capsuleA.setHalfHeight(halfHeightA);
+    capsuleA.setHalfHeight(halfHeightB);
+
+    CollisionList collisions(16);
+
+    // side by side
+    capsuleB.setTranslation((1.01f * totalRadius) * xAxis);
+    if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
+    {
+        std::cout << __FILE__ << ":" << __LINE__
+            << " ERROR: capsule and capsule should NOT touch"
+            << std::endl;
+    }
+    if (ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
+    {
+        std::cout << __FILE__ << ":" << __LINE__
+            << " ERROR: capsule and capsule should NOT touch"
+            << std::endl;
+    }
+
+    // end to end
+    capsuleB.setTranslation((1.01f * totalHalfLength) * xAxis);
+    if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
+    {
+        std::cout << __FILE__ << ":" << __LINE__
+            << " ERROR: capsule and capsule should NOT touch"
+            << std::endl;
+    }
+    if (ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
+    {
+        std::cout << __FILE__ << ":" << __LINE__
+            << " ERROR: capsule and capsule should NOT touch"
+            << std::endl;
+    }
+
+    // rotate B and move it to the side
+    glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
+    capsuleB.setRotation(rotation);
+    capsuleB.setTranslation((1.01f * (totalRadius + capsuleB.getHalfHeight())) * xAxis);
+    if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
+    {
+        std::cout << __FILE__ << ":" << __LINE__
+            << " ERROR: capsule and capsule should NOT touch"
+            << std::endl;
+    }
+    if (ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
+    {
+        std::cout << __FILE__ << ":" << __LINE__
+            << " ERROR: capsule and capsule should NOT touch"
+            << std::endl;
+    }
+
+    if (collisions.size() > 0) {
+        std::cout << __FILE__ << ":" << __LINE__
+            << " ERROR: expected empty collision list but size is " << collisions.size()
+            << std::endl;
+    }
+}
+
+void VerletShapeTests::capsuleTouchesCapsule() {
+    // overlapping capsules
+    float radiusA = 2.0f;
+    float halfHeightA = 3.0f;
+    float radiusB = 3.0f;
+    float halfHeightB = 4.0f;
+
+    float totalRadius = radiusA + radiusB;
+    float totalHalfLength = totalRadius + halfHeightA + halfHeightB;
+
+    // create points for the shapes
+    VerletPoint points[4];
+    for (int i = 0; i < 4; ++i) {
+        points[i]._position = glm::vec3(0.0f);
+    }
+
+    // give the points to the shapes
+    VerletCapsuleShape capsuleA(radiusA, points+0, points+1);
+    VerletCapsuleShape capsuleB(radiusB, points+2, points+3);
+    capsuleA.setHalfHeight(halfHeightA);
+    capsuleB.setHalfHeight(halfHeightB);
+
+    CollisionList collisions(16);
+    int numCollisions = 0;
+
+    { // side by side
+        capsuleB.setTranslation((0.99f * totalRadius) * xAxis);
+        if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
+        {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: capsule and capsule should touch"
+                << std::endl;
+        } else {
+            ++numCollisions;
+        }
+        if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
+        {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: capsule and capsule should touch"
+                << std::endl;
+        } else {
+            ++numCollisions;
+        }
+    }
+
+    { // end to end
+        capsuleB.setTranslation((0.99f * totalHalfLength) * yAxis);
+
+        if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
+        {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: capsule and capsule should touch"
+                << std::endl;
+        } else {
+            ++numCollisions;
+        }
+        if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
+        {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: capsule and capsule should touch"
+                << std::endl;
+        } else {
+            ++numCollisions;
+        }
+    }
+
+    { // rotate B and move it to the side
+        glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
+        capsuleB.setRotation(rotation);
+        capsuleB.setTranslation((0.99f * (totalRadius + capsuleB.getHalfHeight())) * xAxis);
+
+        if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
+        {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: capsule and capsule should touch"
+                << std::endl;
+        } else {
+            ++numCollisions;
+        }
+        if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
+        {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: capsule and capsule should touch"
+                << std::endl;
+        } else {
+            ++numCollisions;
+        }
+    }
+
+    { // again, but this time check collision details
+        float overlap = 0.1f;
+        glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
+        capsuleB.setRotation(rotation);
+        glm::vec3 positionB = ((totalRadius + capsuleB.getHalfHeight()) - overlap) * xAxis;
+        capsuleB.setTranslation(positionB);
+
+        // capsuleA vs capsuleB
+        if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
+        {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: capsule and capsule should touch"
+                << std::endl;
+        } else {
+            ++numCollisions;
+        }
+    
+        CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
+        glm::vec3 expectedPenetration = overlap * xAxis;
+        float inaccuracy = glm::length(collision->_penetration - expectedPenetration);
+        if (fabs(inaccuracy) > EPSILON) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: bad penetration: expected = " << expectedPenetration
+                << " actual = " << collision->_penetration;
+        }
+    
+        glm::vec3 expectedContactPoint = capsuleA.getTranslation() + radiusA * xAxis;
+        inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
+        if (fabs(inaccuracy) > EPSILON) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: bad contactPoint: expected = " << expectedContactPoint
+                << " actual = " << collision->_contactPoint;
+        }
+
+        // capsuleB vs capsuleA
+        if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions))
+        {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: capsule and capsule should touch"
+                << std::endl;
+        } else {
+            ++numCollisions;
+        }
+    
+        collision = collisions.getCollision(numCollisions - 1);
+        expectedPenetration = - overlap * xAxis;
+        inaccuracy = glm::length(collision->_penetration - expectedPenetration);
+        if (fabs(inaccuracy) > EPSILON) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: bad penetration: expected = " << expectedPenetration
+                << " actual = " << collision->_penetration 
+                << std::endl;
+        }
+    
+        expectedContactPoint = capsuleB.getTranslation() - (radiusB + halfHeightB) * xAxis;
+        inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
+        if (fabs(inaccuracy) > EPSILON) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: bad contactPoint: expected = " << expectedContactPoint
+                << " actual = " << collision->_contactPoint 
+                << std::endl;
+        }
+    }
+
+    { // collide cylinder wall against cylinder wall
+        float overlap = 0.137f;
+        float shift = 0.317f * halfHeightA;
+        glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis);
+        capsuleB.setRotation(rotation);
+        glm::vec3 positionB = (totalRadius - overlap) * zAxis + shift * yAxis;
+        capsuleB.setTranslation(positionB);
+
+        // capsuleA vs capsuleB
+        if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions))
+        {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: capsule and capsule should touch"
+                << std::endl;
+        } else {
+            ++numCollisions;
+        }
+    
+        CollisionInfo* collision = collisions.getCollision(numCollisions - 1);
+        glm::vec3 expectedPenetration = overlap * zAxis;
+        float inaccuracy = glm::length(collision->_penetration - expectedPenetration);
+        if (fabs(inaccuracy) > EPSILON) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: bad penetration: expected = " << expectedPenetration
+                << " actual = " << collision->_penetration 
+                << std::endl;
+        }
+    
+        glm::vec3 expectedContactPoint = capsuleA.getTranslation() + radiusA * zAxis + shift * yAxis;
+        inaccuracy = glm::length(collision->_contactPoint - expectedContactPoint);
+        if (fabs(inaccuracy) > EPSILON) {
+            std::cout << __FILE__ << ":" << __LINE__
+                << " ERROR: bad contactPoint: expected = " << expectedContactPoint
+                << " actual = " << collision->_contactPoint 
+                << std::endl;
+        }
+    }
+}
+
+void VerletShapeTests::runAllTests() {
+    setSpherePosition();
+    sphereMissesSphere();
+    sphereTouchesSphere();
+
+    sphereMissesCapsule();
+    sphereTouchesCapsule();
+
+    capsuleMissesCapsule();
+    capsuleTouchesCapsule();
+}
diff --git a/tests/physics/src/VerletShapeTests.h b/tests/physics/src/VerletShapeTests.h
new file mode 100644
index 0000000000..36e2fe0cbd
--- /dev/null
+++ b/tests/physics/src/VerletShapeTests.h
@@ -0,0 +1,30 @@
+//
+//  VerletShapeTests.h
+//  tests/physics/src
+//
+//  Created by Andrew Meadows on 2014.06.18
+//  Copyright 2014 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
+//
+
+#ifndef hifi_VerletShapeTests_h
+#define hifi_VerletShapeTests_h
+
+namespace VerletShapeTests {
+    void setSpherePosition();
+
+    void sphereMissesSphere();
+    void sphereTouchesSphere();
+
+    void sphereMissesCapsule();
+    void sphereTouchesCapsule();
+
+    void capsuleMissesCapsule();
+    void capsuleTouchesCapsule();
+
+    void runAllTests(); 
+}
+
+#endif // hifi_VerletShapeTests_h
diff --git a/tests/physics/src/main.cpp b/tests/physics/src/main.cpp
index ca98f4d546..086bff4dcd 100644
--- a/tests/physics/src/main.cpp
+++ b/tests/physics/src/main.cpp
@@ -9,8 +9,10 @@
 //
 
 #include "ShapeColliderTests.h"
+#include "VerletShapeTests.h"
 
 int main(int argc, char** argv) {
     ShapeColliderTests::runAllTests();
+    VerletShapeTests::runAllTests();
     return 0;
 }
diff --git a/tools/bitstream2json/CMakeLists.txt b/tools/bitstream2json/CMakeLists.txt
index fde80b4d33..d5b82adbd9 100644
--- a/tools/bitstream2json/CMakeLists.txt
+++ b/tools/bitstream2json/CMakeLists.txt
@@ -18,9 +18,14 @@ include(${MACRO_DIR}/SetupHifiProject.cmake)
 setup_hifi_project(${TARGET_NAME} TRUE)
 
 link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
+link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
 link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
 
 include(${MACRO_DIR}/IncludeGLM.cmake)
 include_glm(${TARGET_NAME} "${ROOT_DIR}")
 
+IF (WIN32)
+    target_link_libraries(${TARGET_NAME} Winmm Ws2_32)
+ENDIF(WIN32)
+
 target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script)
diff --git a/tools/json2bitstream/CMakeLists.txt b/tools/json2bitstream/CMakeLists.txt
index 51382d5858..b93c57b582 100644
--- a/tools/json2bitstream/CMakeLists.txt
+++ b/tools/json2bitstream/CMakeLists.txt
@@ -18,9 +18,14 @@ include(${MACRO_DIR}/SetupHifiProject.cmake)
 setup_hifi_project(${TARGET_NAME} TRUE)
 
 link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
+link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
 link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
 
 include(${MACRO_DIR}/IncludeGLM.cmake)
 include_glm(${TARGET_NAME} "${ROOT_DIR}")
 
+IF (WIN32)
+    target_link_libraries(${TARGET_NAME} Winmm Ws2_32)
+ENDIF(WIN32)
+
 target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script)