diff --git a/.eslintrc b/.eslintrc
index 0d67347a8..aacaef356 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -8,7 +8,7 @@
         "max-len": [2, 80, 4],
         "semi": [2, "always"],
         "strict": [2, "never"],
-        "no-console": [2, {"allow": ["log", "warn", "error"]}]
+        "no-console": [2, {"allow": ["log", "warn", "error", "groupCollapsed", "groupEnd"]}]
     },
     "env": {
         "node": true,
diff --git a/playground/index.html b/playground/index.html
index 87cef30b3..3d10a22d0 100644
--- a/playground/index.html
+++ b/playground/index.html
@@ -12,8 +12,16 @@
         <h2>Scratch VM Playground</h2>
         <button id="greenflag">Green flag</button>
         <button id="stopall">Stop</button>
+        <p>
+            <a id="threadexplorer-link" href="#">VM Threads</a><br />
+            <a id="blockexplorer-link" href="#">VM Block Representation</a>
+        </p>
+        <div id="tab-threadexplorer">
+            Thread explorer
+            <pre id="threadexplorer"></pre>
+        </div>
         <div id="tab-blockexplorer">
-            <h3>VM Block Representation</h3>
+            Block explorer
             <pre id="blockexplorer"></pre>
         </div>
     </div>
diff --git a/playground/playground.css b/playground/playground.css
index b20b2bd91..451a7cad5 100644
--- a/playground/playground.css
+++ b/playground/playground.css
@@ -1,6 +1,9 @@
 body {
     background: rgb(36,36,36);
 }
+a {
+    color: rgb(217,217,217);
+}
 #blocks {
     position: absolute;
     left: 40%;
@@ -17,7 +20,7 @@ body {
     bottom: 0;
     width: 35%;
 }
-#blockexplorer {
+#blockexplorer, #threadexplorer {
     position: absolute;
     width: 100%;
     height: 75%;
@@ -28,3 +31,7 @@ body {
     font-family: monospace;
     font-size: 10pt;
 }
+
+#tab-blockexplorer {
+    display: none;
+}
diff --git a/playground/playground.js b/playground/playground.js
index 3926ecb90..5669212d7 100644
--- a/playground/playground.js
+++ b/playground/playground.js
@@ -7,6 +7,11 @@ window.onload = function() {
     var workspace = window.Blockly.inject('blocks', {
         toolbox: toolbox,
         media: '../node_modules/scratch-blocks/media/',
+        zoom: {
+            controls: true,
+            wheel: true,
+            startScale: 0.75
+        },
         colours: {
             workspace: '#334771',
             flyout: '#283856',
@@ -25,20 +30,40 @@ window.onload = function() {
     var flyoutWorkspace = workspace.toolbox_.flyout_.workspace_;
     flyoutWorkspace.addChangeListener(vm.flyoutBlockListener);
 
-    var explorer = document.getElementById('blockexplorer');
+    var blockexplorer = document.getElementById('blockexplorer');
     workspace.addChangeListener(function() {
         // On a change, update the block explorer.
-        explorer.innerHTML = JSON.stringify(vm.runtime.blocks, null, 2);
-        window.hljs.highlightBlock(explorer);
+        blockexplorer.innerHTML = JSON.stringify(vm.runtime.blocks, null, 2);
+        window.hljs.highlightBlock(blockexplorer);
     });
 
-    // Feedback for stacks running.
+    var threadexplorer = document.getElementById('threadexplorer');
+    var cachedThreadJSON = '';
+    var updateThreadExplorer = function () {
+        var newJSON = JSON.stringify(vm.runtime.threads, null, 2);
+        if (newJSON != cachedThreadJSON) {
+            cachedThreadJSON = newJSON;
+            threadexplorer.innerHTML = cachedThreadJSON;
+            window.hljs.highlightBlock(threadexplorer);
+        }
+        window.requestAnimationFrame(updateThreadExplorer);
+    };
+    updateThreadExplorer();
+
+    // Feedback for stacks and blocks running.
     vm.runtime.on('STACK_GLOW_ON', function(blockId) {
         workspace.glowStack(blockId, true);
     });
     vm.runtime.on('STACK_GLOW_OFF', function(blockId) {
         workspace.glowStack(blockId, false);
     });
+    vm.runtime.on('BLOCK_GLOW_ON', function(blockId) {
+        workspace.glowBlock(blockId, true);
+    });
+    vm.runtime.on('BLOCK_GLOW_OFF', function(blockId) {
+        workspace.glowBlock(blockId, false);
+    });
+
 
     // Run threads
     vm.runtime.start();
@@ -50,4 +75,19 @@ window.onload = function() {
     document.getElementById('stopall').addEventListener('click', function() {
         vm.runtime.stopAll();
     });
+
+    var tabBlockExplorer = document.getElementById('tab-blockexplorer');
+    var tabThreadExplorer = document.getElementById('tab-threadexplorer');
+
+    // Handlers to show different explorers.
+    document.getElementById('threadexplorer-link').addEventListener('click',
+        function () {
+            tabBlockExplorer.style.display = 'none';
+            tabThreadExplorer.style.display = 'block';
+        });
+    document.getElementById('blockexplorer-link').addEventListener('click',
+        function () {
+            tabBlockExplorer.style.display = 'block';
+            tabThreadExplorer.style.display = 'none';
+        });
 };
diff --git a/src/blocks/scratch3.js b/src/blocks/scratch3.js
index 6bf327642..a2f132532 100644
--- a/src/blocks/scratch3.js
+++ b/src/blocks/scratch3.js
@@ -23,7 +23,6 @@ Scratch3Blocks.prototype.getPrimitives = function() {
 };
 
 Scratch3Blocks.prototype.repeat = function(argValues, util) {
-    console.log('Running: control_repeat');
     // Initialize loop
     if (util.stackFrame.loopCounter === undefined) {
         util.stackFrame.loopCounter = parseInt(argValues[0]); // @todo arg
@@ -37,12 +36,10 @@ Scratch3Blocks.prototype.repeat = function(argValues, util) {
 };
 
 Scratch3Blocks.prototype.forever = function(argValues, util) {
-    console.log('Running: control_forever');
     util.startSubstack();
 };
 
 Scratch3Blocks.prototype.wait = function(argValues, util) {
-    console.log('Running: control_wait');
     util.yield();
     util.timeout(function() {
         util.done();
@@ -50,23 +47,19 @@ Scratch3Blocks.prototype.wait = function(argValues, util) {
 };
 
 Scratch3Blocks.prototype.stop = function() {
-    console.log('Running: control_stop');
     // @todo - don't use this.runtime
     this.runtime.stopAll();
 };
 
 Scratch3Blocks.prototype.whenFlagClicked = function() {
-    console.log('Running: event_whenflagclicked');
     // No-op
 };
 
 Scratch3Blocks.prototype.whenBroadcastReceived = function() {
-    console.log('Running: event_whenbroadcastreceived');
     // No-op
 };
 
 Scratch3Blocks.prototype.broadcast = function(argValues, util) {
-    console.log('Running: event_broadcast');
     util.startHats(function(hat) {
         if (hat.opcode === 'event_whenbroadcastreceived') {
             var shadows = hat.fields.CHOICE.blocks;
diff --git a/src/blocks/wedo2.js b/src/blocks/wedo2.js
index 86c73de07..5074b26a1 100644
--- a/src/blocks/wedo2.js
+++ b/src/blocks/wedo2.js
@@ -144,11 +144,9 @@ WeDo2Blocks.prototype.setColor = function(argValues, util) {
 };
 
 WeDo2Blocks.prototype.whenDistanceClose = function() {
-    console.log('Running: wedo_whendistanceclose');
 };
 
 WeDo2Blocks.prototype.whenTilt = function() {
-    console.log('Running: wedo_whentilt');
 };
 
 module.exports = WeDo2Blocks;
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 9531eadc7..c4aa06e49 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -199,7 +199,13 @@ Runtime.prototype.startDistanceSensors = function () {
 Runtime.prototype.stopAll = function () {
     var threadsCopy = this.threads.slice();
     while (threadsCopy.length > 0) {
-        this._removeThread(threadsCopy.pop());
+        var poppedThread = threadsCopy.pop();
+        // Unglow any blocks on this thread's stack.
+        for (var i = 0; i < poppedThread.stack.length; i++) {
+            this.glowBlock(poppedThread.stack[i], false);
+        }
+        // Actually remove the thread.
+        this._removeThread(poppedThread);
     }
     // @todo call stop function in all extensions/packages/WeDo stub
     if (window.native) {
diff --git a/src/engine/sequencer.js b/src/engine/sequencer.js
index 95a28f7bf..a1572c3b3 100644
--- a/src/engine/sequencer.js
+++ b/src/engine/sequencer.js
@@ -24,6 +24,12 @@ function Sequencer (runtime) {
  */
 Sequencer.WORK_TIME = 10;
 
+/**
+ * If set, block calls, args, and return values will be logged to the console.
+ * @const {boolean}
+ */
+Sequencer.DEBUG_BLOCK_CALLS = true;
+
 /**
  * Step through all threads in `this.threads`, running them in order.
  * @return {Array.<Thread>} All threads which have finished in this iteration.
@@ -134,6 +140,8 @@ Sequencer.prototype.stepThread = function (thread) {
         // Pop the stack and stack frame
         thread.stack.pop();
         thread.stackFrames.pop();
+        // Stop showing run feedback in the editor.
+        instance.runtime.glowBlock(currentBlock, false);
     };
 
     /**
@@ -199,6 +207,9 @@ Sequencer.prototype.stepThread = function (thread) {
         }
     }
 
+    // Start showing run feedback in the editor.
+    this.runtime.glowBlock(currentBlock, true);
+
     if (!opcode) {
         console.warn('Could not get opcode for block: ' + currentBlock);
     }
@@ -208,9 +219,15 @@ Sequencer.prototype.stepThread = function (thread) {
             console.warn('Could not get implementation for opcode: ' + opcode);
         }
         else {
+            if (Sequencer.DEBUG_BLOCK_CALLS) {
+                console.groupCollapsed('Executing: ' + opcode);
+                console.log('with arguments: ', argValues);
+                console.log('and stack frame: ', currentStackFrame);
+            }
+            var blockFunctionReturnValue = null;
             try {
                 // @todo deal with the return value
-                blockFunction(argValues, {
+                blockFunctionReturnValue = blockFunction(argValues, {
                     yield: threadYieldCallback,
                     done: threadDoneCallback,
                     timeout: YieldTimers.timeout,
@@ -233,6 +250,11 @@ Sequencer.prototype.stepThread = function (thread) {
                     // Thread executed without yielding - move to done
                     threadDoneCallback();
                 }
+                if (Sequencer.DEBUG_BLOCK_CALLS) {
+                    console.log('ending stack frame: ', currentStackFrame);
+                    console.log('returned: ', blockFunctionReturnValue);
+                    console.groupEnd();
+                }
             }
         }
     }