diff --git a/examples/tests/controllerInterfaceTest.js b/examples/tests/controllerInterfaceTest.js
index fa8cf48b9b..48ad8f0879 100644
--- a/examples/tests/controllerInterfaceTest.js
+++ b/examples/tests/controllerInterfaceTest.js
@@ -1,4 +1,12 @@
 ControllerTest = function() {
+    var standard = Controller.Standard;
+    var actions = Controller.Actions;
+    this.mappingEnabled = false;
+    this.mapping = Controller.newMapping();
+    this.mapping.from(standard.RX).to(actions.StepYaw);
+    this.mapping.enable();
+    this.mappingEnabled = true;
+
 
     print("Actions");
     for (var prop in Controller.Actions) {
@@ -24,6 +32,9 @@ ControllerTest = function() {
 }
 
 ControllerTest.prototype.onCleanup = function() {
+    if (this.mappingEnabled) {
+        this.mapping.disable();
+    }
 }
 
 
diff --git a/interface/resources/qml/ScrollingGraph.qml b/interface/resources/qml/ScrollingGraph.qml
index b5eaac6f89..26ca9a61ff 100644
--- a/interface/resources/qml/ScrollingGraph.qml
+++ b/interface/resources/qml/ScrollingGraph.qml
@@ -3,12 +3,12 @@ import QtQuick.Controls 1.0
 import QtQuick.Layouts 1.0
 import QtQuick.Dialogs 1.0
 
-Item {
+Rectangle {
     id: root
     property int size: 64
     width: size
     height: size
-
+    color: 'black'
     property int controlId: 0
     property real value: 0.5
     property int scrollWidth: 1
@@ -16,7 +16,7 @@ Item {
     property real max: 1.0
     property bool log: false
     property real range: max - min
-    property color color: 'blue'
+    property color lineColor: 'yellow'
     property bool bar: false
     property real lastHeight: -1
     property string label: ""
@@ -49,19 +49,21 @@ Item {
        Text {
            anchors.top: parent.top
            text: root.label
-
+           color: 'white'
        }
 
        Text {
            anchors.right: parent.right
            anchors.top: parent.top
            text: root.max
+           color: 'white'
        }
 
        Text {
            anchors.right: parent.right
            anchors.bottom: parent.bottom
            text: root.min
+           color: 'white'
        }
 
        function scroll() {
@@ -92,7 +94,7 @@ Item {
 
            ctx.beginPath();
            ctx.lineWidth = 1
-           ctx.strokeStyle = root.color
+           ctx.strokeStyle = root.lineColor
            ctx.moveTo(canvas.width - root.scrollWidth, root.lastHeight).lineTo(canvas.width, currentHeight)
            ctx.stroke()
            ctx.restore()
diff --git a/interface/resources/qml/TestControllers.qml b/interface/resources/qml/TestControllers.qml
index a5deaed159..f1b8640c02 100644
--- a/interface/resources/qml/TestControllers.qml
+++ b/interface/resources/qml/TestControllers.qml
@@ -20,8 +20,20 @@ HifiControls.VrDialog {
     property var standard: Controller.Standard
     property var hydra: null
     property var testMapping: null
+    property bool testMappingEnabled: false
     property var xbox: null
 
+    function buildMapping() {
+        testMapping = Controller.newMapping();
+        testMapping.from(standard.RY).invert().to(actions.Pitch);
+        testMapping.makeAxis(standard.LB, standard.RB).to(actions.Yaw);
+        testMapping.from(standard.RX).to(actions.StepYaw);
+    }
+
+    function toggleMapping() {
+        testMapping.enable(!testMappingEnabled);
+        testMappingEnabled = !testMappingEnabled;
+    }
 
     Component.onCompleted: {
         enabled = true
@@ -49,110 +61,18 @@ HifiControls.VrDialog {
 
         Row {
             spacing: 8
-            Button {
-                text: "Standard Mapping"
-                onClicked: {
-                    var mapping = Controller.newMapping("Default");
-                    mapping.from(standard.LX).to(actions.TranslateX);
-                    mapping.from(standard.LY).to(actions.TranslateZ);
-                    mapping.from(standard.RY).to(actions.Pitch);
-                    mapping.from(standard.RX).to(actions.Yaw);
-                    mapping.from(standard.DU).scale(0.5).to(actions.LONGITUDINAL_FORWARD);
-                    mapping.from(standard.DD).scale(0.5).to(actions.LONGITUDINAL_BACKWARD);
-                    mapping.from(standard.DL).scale(0.5).to(actions.LATERAL_LEFT);
-                    mapping.from(standard.DR).scale(0.5).to(actions.LATERAL_RIGHT);
-                    mapping.from(standard.X).to(actions.VERTICAL_DOWN);
-                    mapping.from(standard.Y).to(actions.VERTICAL_UP);
-                    mapping.from(standard.RT).scale(0.1).to(actions.BOOM_IN);
-                    mapping.from(standard.LT).scale(0.1).to(actions.BOOM_OUT);
-                    mapping.from(standard.B).to(actions.ACTION1);
-                    mapping.from(standard.A).to(actions.ACTION2);
-                    mapping.from(standard.RB).to(actions.SHIFT);
-                    mapping.from(standard.Back).to(actions.TOGGLE_MUTE);
-                    mapping.from(standard.Start).to(actions.CONTEXT_MENU);
-                    Controller.enableMapping("Default");
-                    enabled = false;
-                    text = "Standard Built"
-                }
-            }
             
             Button {
-                text: root.xbox ? "XBox Mapping" : "XBox not found"
-                property bool built: false
-                enabled: root.xbox && !built
+                text: !root.testMapping ? "Build Mapping" : (root.testMappingEnabled ? "Disable Mapping" : "Enable Mapping")
                 onClicked: {
-                    var mapping = Controller.newMapping();
-                    mapping.from(xbox.A).to(standard.A);
-                    mapping.from(xbox.B).to(standard.B);
-                    mapping.from(xbox.X).to(standard.X);
-                    mapping.from(xbox.Y).to(standard.Y);
-                    mapping.from(xbox.Up).to(standard.DU);
-                    mapping.from(xbox.Down).to(standard.DD);
-                    mapping.from(xbox.Left).to(standard.DL);
-                    mapping.from(xbox.Right).to(standard.Right);
-                    mapping.from(xbox.LB).to(standard.LB);
-                    mapping.from(xbox.RB).to(standard.RB);
-                    mapping.from(xbox.LS).to(standard.LS);
-                    mapping.from(xbox.RS).to(standard.RS);
-                    mapping.from(xbox.Start).to(standard.Start);
-                    mapping.from(xbox.Back).to(standard.Back);
-                    mapping.from(xbox.LY).to(standard.LY);
-                    mapping.from(xbox.LX).to(standard.LX);
-                    mapping.from(xbox.RY).to(standard.RY);
-                    mapping.from(xbox.RX).to(standard.RX);
-                    mapping.from(xbox.LT).to(standard.LT);
-                    mapping.from(xbox.RT).to(standard.RT);
-                    mapping.enable();
-                    built = false;
-                    text = "XBox Built"
+
+                    if (!root.testMapping) {
+                        root.buildMapping()
+                    } else {
+                        root.toggleMapping();
+                    }
                 }
             }
-
-            Button {
-                text: root.hydra ? "Hydra Mapping" : "Hydra Not Found"
-                property bool built: false
-                enabled: root.hydra && !built
-                onClicked: {
-                    var mapping = Controller.newMapping();
-                    mapping.from(hydra.LY).invert().to(standard.LY);
-                    mapping.from(hydra.LX).to(standard.LX);
-                    mapping.from(hydra.RY).invert().to(standard.RY);
-                    mapping.from(hydra.RX).to(standard.RX);
-                    mapping.from(hydra.LT).to(standard.LT);
-                    mapping.from(hydra.RT).to(standard.RT);
-                    mapping.enable();
-                    built = false;
-                    text = "Hydra Built"
-                }
-            }
-
-            Button {
-                text: "Test Mapping"
-                onClicked: {
-                    var mapping = Controller.newMapping();
-                    // Inverting a value
-                    mapping.from(standard.RY).invert().to(standard.RY);
-                    mapping.makeAxis(standard.LB, standard.RB).to(actions.Yaw);
-                    testMapping = mapping;
-                    enabled = false
-                    text = "Built"
-                }
-            }
-
-            Button {
-                text: "Enable Mapping"
-                onClicked: root.testMapping.enable()
-            }
-
-            Button {
-                text: "Disable Mapping"
-                onClicked: root.testMapping.disable()
-            }
-
-            Button {
-                text: "Enable Mapping"
-                onClicked: print(Controller.getValue(root.xbox.LY));
-            }
         }
 
         Row {
@@ -170,25 +90,32 @@ HifiControls.VrDialog {
             ScrollingGraph {
                 controlId: Controller.Actions.Yaw
                 label: "Yaw"
-                min: -3.0
-                max: 3.0
-                size: 128
+                min: -2.0
+                max: 2.0
+                size: 64
             }
 
             ScrollingGraph {
-                controlId: Controller.Actions.YAW_LEFT
+                controlId: Controller.Actions.YawLeft
                 label: "Yaw Left"
-                min: -3.0
-                max: 3.0
-                size: 128
+                min: -2.0
+                max: 2.0
+                size: 64
             }
 
             ScrollingGraph {
-                controlId: Controller.Actions.YAW_RIGHT
+                controlId: Controller.Actions.YawRight
                 label: "Yaw Right"
-                min: -3.0
-                max: 3.0
-                size: 128
+                min: -2.0
+                max: 2.0
+                size: 64
+            }
+            ScrollingGraph {
+                controlId: Controller.Actions.StepYaw
+                label: "StepYaw"
+                min: -2.0
+                max: 2.0
+                size: 64
             }
         }
     }
diff --git a/interface/resources/qml/controller/Standard.qml b/interface/resources/qml/controller/Standard.qml
index cee79fe50c..45e4febfa2 100644
--- a/interface/resources/qml/controller/Standard.qml
+++ b/interface/resources/qml/controller/Standard.qml
@@ -29,7 +29,7 @@ Item {
     // Left primary
     ToggleButton {
         x: parent.width - width; y: parent.height - height; 
-        controlId: root.device.RB
+        controlId: root.device.RightPrimaryThumb
         width: 16 * root.scale; height: 16 * root.scale
     }
 }
diff --git a/libraries/controllers/src/controllers/Actions.cpp b/libraries/controllers/src/controllers/Actions.cpp
index 7954ab5522..b0d2d24edf 100644
--- a/libraries/controllers/src/controllers/Actions.cpp
+++ b/libraries/controllers/src/controllers/Actions.cpp
@@ -42,6 +42,12 @@ namespace controller {
                 makeAxisPair(Action::ROLL, "Roll"),
                 makeAxisPair(Action::PITCH, "Pitch"),
                 makeAxisPair(Action::YAW, "Yaw"),
+                makeAxisPair(Action::STEP_YAW, "StepYaw"),
+                makeAxisPair(Action::STEP_PITCH, "StepPitch"),
+                makeAxisPair(Action::STEP_ROLL, "StepRoll"),
+                makeAxisPair(Action::STEP_TRANSLATE_X, "StepTranslateX"),
+                makeAxisPair(Action::STEP_TRANSLATE_X, "StepTranslateY"),
+                makeAxisPair(Action::STEP_TRANSLATE_X, "StepTranslateZ"),
                 makeAxisPair(Action::LONGITUDINAL_BACKWARD, "Backward"),
                 makeAxisPair(Action::LONGITUDINAL_FORWARD, "Forward"),
                 makeAxisPair(Action::LATERAL_LEFT, "StrafeLeft"),
@@ -67,7 +73,6 @@ namespace controller {
                 makeButtonPair(Action::CONTEXT_MENU, "ContextMenu"),
                 makeButtonPair(Action::TOGGLE_MUTE, "ToggleMute"),
 
-
                 // Deprecated aliases
                 // FIXME remove after we port all scripts
                 makeAxisPair(Action::LONGITUDINAL_BACKWARD, "LONGITUDINAL_BACKWARD"),
diff --git a/libraries/controllers/src/controllers/Actions.h b/libraries/controllers/src/controllers/Actions.h
index 77a772de9e..47f04141f3 100644
--- a/libraries/controllers/src/controllers/Actions.h
+++ b/libraries/controllers/src/controllers/Actions.h
@@ -27,6 +27,16 @@ enum class Action {
     ROTATE_Y, YAW = ROTATE_Y,
     ROTATE_Z, ROLL = ROTATE_Z,
 
+    STEP_YAW,
+    // FIXME does this have a use case?
+    STEP_PITCH,
+    // FIXME does this have a use case?
+    STEP_ROLL,
+
+    STEP_TRANSLATE_X,
+    STEP_TRANSLATE_Y,
+    STEP_TRANSLATE_Z,
+
     TRANSLATE_CAMERA_Z,
     NUM_COMBINED_AXES,
 
diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp
index d80952a5d9..ae806ed613 100755
--- a/libraries/controllers/src/controllers/UserInputMapper.cpp
+++ b/libraries/controllers/src/controllers/UserInputMapper.cpp
@@ -29,7 +29,6 @@ namespace controller {
 
 // Default contruct allocate the poutput size with the current hardcoded action channels
 controller::UserInputMapper::UserInputMapper() {
-    _activeMappings.push_back(_defaultMapping);
     _standardController = std::make_shared<StandardController>();
     registerDevice(new ActionsDevice());
     registerDevice(_standardController.get());
@@ -317,6 +316,7 @@ int UserInputMapper::recordDeviceOfType(const QString& deviceName) {
 }
 
 void UserInputMapper::registerDevice(InputDevice* device) {
+    Locker locker(_lock);
     if (device->_deviceID == Input::INVALID_DEVICE) {
         device->_deviceID = getFreeDeviceID();
     }
@@ -354,13 +354,7 @@ void UserInputMapper::registerDevice(InputDevice* device) {
     auto mapping = loadMapping(device->getDefaultMappingConfig());
     if (mapping) {
         _mappingsByDevice[deviceID] = mapping;
-        auto& defaultRoutes = _defaultMapping->routes;
-
-        // New routes for a device get injected IN FRONT of existing routes.  Routes
-        // are processed in order so this ensures that the standard -> action processing 
-        // takes place after all of the hardware -> standard or hardware -> action processing
-        // because standard -> action is the first set of routes added.
-        defaultRoutes.insert(defaultRoutes.begin(), mapping->routes.begin(), mapping->routes.end());
+        enableMapping(mapping);
     }
 
     emit hardwareChanged();
@@ -368,6 +362,7 @@ void UserInputMapper::registerDevice(InputDevice* device) {
 
 // FIXME remove the associated device mappings
 void UserInputMapper::removeDevice(int deviceID) {
+    Locker locker(_lock);
     auto proxyEntry = _registeredDevices.find(deviceID);
     if (_registeredDevices.end() == proxyEntry) {
         qCWarning(controllers) << "Attempted to remove unknown device " << deviceID;
@@ -376,15 +371,7 @@ void UserInputMapper::removeDevice(int deviceID) {
     auto proxy = proxyEntry->second;
     auto mappingsEntry = _mappingsByDevice.find(deviceID);
     if (_mappingsByDevice.end() != mappingsEntry) {
-        const auto& mapping = mappingsEntry->second;
-        const auto& deviceRoutes = mapping->routes;
-        std::set<Route::Pointer> routeSet(deviceRoutes.begin(), deviceRoutes.end());
-
-        auto& defaultRoutes = _defaultMapping->routes;
-        std::remove_if(defaultRoutes.begin(), defaultRoutes.end(), [&](Route::Pointer route)->bool {
-            return routeSet.count(route) != 0;
-        });
-
+        disableMapping(mappingsEntry->second);
         _mappingsByDevice.erase(mappingsEntry);
     }
 
@@ -395,6 +382,7 @@ void UserInputMapper::removeDevice(int deviceID) {
 
 
 DeviceProxy::Pointer UserInputMapper::getDeviceProxy(const Input& input) {
+    Locker locker(_lock);
     auto device = _registeredDevices.find(input.getDevice());
     if (device != _registeredDevices.end()) {
         return (device->second);
@@ -404,6 +392,7 @@ DeviceProxy::Pointer UserInputMapper::getDeviceProxy(const Input& input) {
 }
 
 QString UserInputMapper::getDeviceName(uint16 deviceID) { 
+    Locker locker(_lock);
     if (_registeredDevices.find(deviceID) != _registeredDevices.end()) {
         return _registeredDevices[deviceID]->_name;
     }
@@ -411,6 +400,7 @@ QString UserInputMapper::getDeviceName(uint16 deviceID) {
 }
 
 int UserInputMapper::findDevice(QString name) const {
+    Locker locker(_lock);
     for (auto device : _registeredDevices) {
         if (device.second->_name == name) {
             return device.first;
@@ -420,6 +410,7 @@ int UserInputMapper::findDevice(QString name) const {
 }
 
 QVector<QString> UserInputMapper::getDeviceNames() {
+    Locker locker(_lock);
     QVector<QString> result;
     for (auto device : _registeredDevices) {
         QString deviceName = device.second->_name.split(" (")[0];
@@ -433,6 +424,7 @@ int UserInputMapper::findAction(const QString& actionName) const {
 }
 
 Input UserInputMapper::findDeviceInput(const QString& inputName) const {
+    Locker locker(_lock);
     // Split the full input name as such: deviceName.inputName
     auto names = inputName.split('.');
 
@@ -472,6 +464,7 @@ void fixBisectedAxis(float& full, float& negative, float& positive) {
 }
 
 void UserInputMapper::update(float deltaTime) {
+    Locker locker(_lock);
     // Reset the axis state for next loop
     for (auto& channel : _actionStates) {
         channel = 0.0f;
@@ -505,11 +498,13 @@ void UserInputMapper::update(float deltaTime) {
 }
 
 Input::NamedVector UserInputMapper::getAvailableInputs(uint16 deviceID) const {
+    Locker locker(_lock);
     auto iterator = _registeredDevices.find(deviceID);
     return iterator->second->getAvailabeInputs();
 }
 
 QVector<Action> UserInputMapper::getAllActions() const {
+    Locker locker(_lock);
     QVector<Action> actions;
     for (auto i = 0; i < toInt(Action::NUM_ACTIONS); i++) {
         actions.append(Action(i));
@@ -518,6 +513,7 @@ QVector<Action> UserInputMapper::getAllActions() const {
 }
 
 QString UserInputMapper::getActionName(Action action) const {
+    Locker locker(_lock);
     for (auto actionPair : getActionInputs()) {
         if (actionPair.first.channel == toInt(action)) {
             return actionPair.second;
@@ -528,6 +524,7 @@ QString UserInputMapper::getActionName(Action action) const {
 
 
 QVector<QString> UserInputMapper::getActionNames() const {
+    Locker locker(_lock);
     QVector<QString> result;
     for (auto actionPair : getActionInputs()) {
         result << actionPair.second;
@@ -645,74 +642,87 @@ void UserInputMapper::runMappings() {
     }
 
     // Now process the current values for each level of the stack
-    for (auto& mapping : _activeMappings) {
-        for (const auto& route : mapping->routes) {
-            if (route->conditional) {
-                if (!route->conditional->satisfied()) {
-                    continue;
-                }
-            }
+    for (const auto& route : _deviceRoutes) {
+        if (!route) {
+            continue;
+        }
+        applyRoute(route);
+    }
 
-            auto source = route->source;
-            if (_overrides.count(source)) {
-                source = _overrides[source];
-            }
+    for (const auto& route : _standardRoutes) {
+        if (!route) {
+            continue;
+        }
+        applyRoute(route);
+    }
 
-            // Endpoints can only be read once (though a given mapping can route them to 
-            // multiple places).  Consider... If the default is to wire the A button to JUMP
-            // and someone else wires it to CONTEXT_MENU, I don't want both to occur when 
-            // I press the button.  The exception is if I'm wiring a control back to itself
-            // in order to adjust my interface, like inverting the Y axis on an analog stick
-            if (!source->readable()) {
-                continue;
-            }
+}
 
 
-            auto input = source->getInput();
-            float value = source->value();
-            if (value != 0.0) {
-                int i = 0;
-            }
-
-            auto destination = route->destination;
-            // THis could happen if the route destination failed to create
-            // FIXME: Maybe do not create the route if the destination failed and avoid this case ?
-            if (!destination) {
-                continue;
-            }
-
-            // FIXME?, should come before or after the override logic?
-            if (!destination->writeable()) {
-                continue;
-            }
-
-            // Only consume the input if the route isn't a loopback.
-            // This allows mappings like `mapping.from(xbox.RY).invert().to(xbox.RY);`
-            bool loopback = (source->getInput() == destination->getInput()) && (source->getInput() != Input::INVALID_INPUT);
-            // Each time we loop back we re-write the override 
-            if (loopback) {
-                _overrides[source] = destination = std::make_shared<StandardEndpoint>(source->getInput());
-            }
-
-            // Fetch the value, may have been overriden by previous loopback routes
-            if (source->isPose()) {
-                Pose value = getPose(source);
-                // no filters yet for pose
-                destination->apply(value, Pose(), source);
-            } else {
-                // Fetch the value, may have been overriden by previous loopback routes
-                float value = getValue(source);
-
-                // Apply each of the filters.
-                for (const auto& filter : route->filters) {
-                    value = filter->apply(value);
-                }
-
-                destination->apply(value, 0, source);
-            }
+void UserInputMapper::applyRoute(const Route::Pointer& route) {
+    if (route->conditional) {
+        if (!route->conditional->satisfied()) {
+            return;
         }
     }
 
+    auto source = route->source;
+    if (_overrides.count(source)) {
+        source = _overrides[source];
+    }
+
+    // Endpoints can only be read once (though a given mapping can route them to 
+    // multiple places).  Consider... If the default is to wire the A button to JUMP
+    // and someone else wires it to CONTEXT_MENU, I don't want both to occur when 
+    // I press the button.  The exception is if I'm wiring a control back to itself
+    // in order to adjust my interface, like inverting the Y axis on an analog stick
+    if (!source->readable()) {
+        return;
+    }
+
+
+    auto input = source->getInput();
+    float value = source->value();
+    if (value != 0.0) {
+        int i = 0;
+    }
+
+    auto destination = route->destination;
+    // THis could happen if the route destination failed to create
+    // FIXME: Maybe do not create the route if the destination failed and avoid this case ?
+    if (!destination) {
+        return;
+    }
+
+    // FIXME?, should come before or after the override logic?
+    if (!destination->writeable()) {
+        return;
+    }
+
+    // Only consume the input if the route isn't a loopback.
+    // This allows mappings like `mapping.from(xbox.RY).invert().to(xbox.RY);`
+    bool loopback = (source->getInput() == destination->getInput()) && (source->getInput() != Input::INVALID_INPUT);
+    // Each time we loop back we re-write the override 
+    if (loopback) {
+        _overrides[source] = destination = std::make_shared<StandardEndpoint>(source->getInput());
+    }
+
+    // Fetch the value, may have been overriden by previous loopback routes
+    if (source->isPose()) {
+        Pose value = getPose(source);
+        // no filters yet for pose
+        destination->apply(value, Pose(), source);
+    } else {
+        // Fetch the value, may have been overriden by previous loopback routes
+        float value = getValue(source);
+
+        // Apply each of the filters.
+        for (const auto& filter : route->filters) {
+            value = filter->apply(value);
+        }
+
+        destination->apply(value, 0, source);
+    }
 }
 
 Endpoint::Pointer UserInputMapper::endpointFor(const QJSValue& endpoint) {
@@ -744,6 +754,7 @@ Endpoint::Pointer UserInputMapper::endpointFor(const QScriptValue& endpoint) {
 }
 
 Endpoint::Pointer UserInputMapper::endpointFor(const Input& inputId) const {
+    Locker locker(_lock);
     auto iterator = _endpointsByInput.find(inputId);
     if (_endpointsByInput.end() == iterator) {
         qWarning() << "Unknown input: " << QString::number(inputId.getID(), 16);
@@ -767,6 +778,7 @@ Endpoint::Pointer UserInputMapper::compositeEndpointFor(Endpoint::Pointer first,
 
 
 Mapping::Pointer UserInputMapper::newMapping(const QString& mappingName) {
+    Locker locker(_lock);
     if (_mappingsByName.count(mappingName)) {
         qCWarning(controllers) << "Refusing to recreate mapping named " << mappingName;
     }
@@ -799,8 +811,8 @@ Mapping::Pointer UserInputMapper::newMapping(const QString& mappingName) {
 //    return result;
 //}
 
-
 void UserInputMapper::enableMapping(const QString& mappingName, bool enable) {
+    Locker locker(_lock);
     qCDebug(controllers) << "Attempting to enable mapping " << mappingName;
     auto iterator = _mappingsByName.find(mappingName);
     if (_mappingsByName.end() == iterator) {
@@ -810,18 +822,14 @@ void UserInputMapper::enableMapping(const QString& mappingName, bool enable) {
 
     auto mapping = iterator->second;
     if (enable) {
-        _activeMappings.push_front(mapping);
+        enableMapping(mapping);
     } else {
-        auto activeIterator = std::find(_activeMappings.begin(), _activeMappings.end(), mapping);
-        if (_activeMappings.end() == activeIterator) {
-            qCWarning(controllers) << "Attempted to disable inactive mapping " << mappingName;
-            return;
-        }
-        _activeMappings.erase(activeIterator);
+        disableMapping(mapping);
     }
 }
 
 float UserInputMapper::getValue(const Endpoint::Pointer& endpoint) const {
+    Locker locker(_lock);
     auto valuesIterator = _overrides.find(endpoint);
     if (_overrides.end() != valuesIterator) {
         return valuesIterator->second->value();
@@ -854,6 +862,7 @@ Pose UserInputMapper::getPose(const Input& input) const {
 }
 
 Mapping::Pointer UserInputMapper::loadMapping(const QString& jsonFile) {
+    Locker locker(_lock);
     if (jsonFile.isEmpty()) {
         return Mapping::Pointer();
     }
@@ -1102,6 +1111,36 @@ Mapping::Pointer UserInputMapper::parseMapping(const QString& json) {
     return parseMapping(doc.object());
 }
 
+
+void UserInputMapper::enableMapping(const Mapping::Pointer& mapping) {
+    Locker locker(_lock);
+    // New routes for a device get injected IN FRONT of existing routes.  Routes
+    // are processed in order so this ensures that the standard -> action processing 
+    // takes place after all of the hardware -> standard or hardware -> action processing
+    // because standard -> action is the first set of routes added.
+    for (auto route : mapping->routes) {
+        if (route->source->getInput().device == STANDARD_DEVICE) {
+            _standardRoutes.push_front(route);
+        } else {
+            _deviceRoutes.push_front(route);
+        }
+    }
+}
+
+void UserInputMapper::disableMapping(const Mapping::Pointer& mapping) {
+    Locker locker(_lock);
+    const auto& deviceRoutes = mapping->routes;
+    std::set<Route::Pointer> routeSet(deviceRoutes.begin(), deviceRoutes.end());
+
+    // FIXME this seems to result in empty route pointers... need to find a better way to remove them.
+    std::remove_if(_deviceRoutes.begin(), _deviceRoutes.end(), [&](Route::Pointer route)->bool {
+        return routeSet.count(route) != 0;
+    });
+    std::remove_if(_standardRoutes.begin(), _standardRoutes.end(), [&](Route::Pointer route)->bool {
+        return routeSet.count(route) != 0;
+    });
+}
+
 }
 
 #include "UserInputMapper.moc"
diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h
index 345bba8c2b..70cd227e05 100644
--- a/libraries/controllers/src/controllers/UserInputMapper.h
+++ b/libraries/controllers/src/controllers/UserInputMapper.h
@@ -15,6 +15,7 @@
 #include <unordered_set>
 #include <functional>
 #include <memory>
+#include <mutex>
 
 #include <QtQml/QJSValue>
 #include <QtScript/QScriptValue>
@@ -119,10 +120,8 @@ namespace controller {
         void hardwareChanged();
 
     protected:
-        virtual void runMappings();
         // GetFreeDeviceID should be called before registering a device to use an ID not used by a different device.
         uint16 getFreeDeviceID() { return _nextFreeDeviceID++; }
-
         InputDevice::Pointer _standardController;
         DevicesMap _registeredDevices;
         uint16 _nextFreeDeviceID = STANDARD_DEVICE + 1;
@@ -142,6 +141,11 @@ namespace controller {
 
         friend class RouteBuilderProxy;
         friend class MappingBuilderProxy;
+
+        void runMappings();
+        void applyRoute(const Route::Pointer& route);
+        void enableMapping(const Mapping::Pointer& mapping);
+        void disableMapping(const Mapping::Pointer& mapping);
         Endpoint::Pointer endpointFor(const QJSValue& endpoint);
         Endpoint::Pointer endpointFor(const QScriptValue& endpoint);
         Endpoint::Pointer endpointFor(const Input& endpoint) const;
@@ -163,9 +167,14 @@ namespace controller {
 
         EndpointOverrideMap _overrides;
         MappingNameMap _mappingsByName;
-        Mapping::Pointer _defaultMapping{ std::make_shared<Mapping>("Default") };
         MappingDeviceMap _mappingsByDevice;
-        MappingStack _activeMappings;
+
+        Route::List _deviceRoutes;
+        Route::List _standardRoutes;
+
+        using Locker = std::unique_lock<std::recursive_mutex>;
+
+        mutable std::recursive_mutex _lock;
     };
 
 }