diff --git a/src/engine/blocks.js b/src/engine/blocks.js
index 93b865770..a767c75d6 100644
--- a/src/engine/blocks.js
+++ b/src/engine/blocks.js
@@ -198,7 +198,7 @@ class Blocks {
         // UI event: clicked scripts toggle in the runtime.
         if (e.element === 'stackclick') {
             if (optRuntime) {
-                optRuntime.toggleScript(e.blockId, {showVisualReport: true});
+                optRuntime.toggleScript(e.blockId, {stackClick: true});
             }
             return;
         }
diff --git a/src/engine/execute.js b/src/engine/execute.js
index 83530d380..1f5a25b35 100644
--- a/src/engine/execute.js
+++ b/src/engine/execute.js
@@ -74,14 +74,17 @@ const execute = function (sequencer, thread) {
             // Hat predicate was evaluated.
             if (runtime.getIsEdgeActivatedHat(opcode)) {
                 // If this is an edge-activated hat, only proceed if
-                // the value is true and used to be false.
-                const oldEdgeValue = runtime.updateEdgeActivatedValue(
-                    currentBlockId,
-                    resolvedValue
-                );
-                const edgeWasActivated = !oldEdgeValue && resolvedValue;
-                if (!edgeWasActivated) {
-                    sequencer.retireThread(thread);
+                // the value is true and used to be false, or the stack was activated
+                // explicitly via stack click
+                if (!thread.stackClick) {
+                    const oldEdgeValue = runtime.updateEdgeActivatedValue(
+                        currentBlockId,
+                        resolvedValue
+                    );
+                    const edgeWasActivated = !oldEdgeValue && resolvedValue;
+                    if (!edgeWasActivated) {
+                        sequencer.retireThread(thread);
+                    }
                 }
             } else {
                 // Not an edge-activated hat: retire the thread
@@ -94,7 +97,7 @@ const execute = function (sequencer, thread) {
             // In a non-hat, report the value visually if necessary if
             // at the top of the thread stack.
             if (typeof resolvedValue !== 'undefined' && thread.atStackTop()) {
-                if (thread.showVisualReport) {
+                if (thread.stackClick) {
                     runtime.visualReport(currentBlockId, resolvedValue);
                 }
                 if (thread.updateMonitor) {
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 7983a4565..b0a1b482a 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -399,19 +399,19 @@ class Runtime extends EventEmitter {
      * @param {!string} id ID of block that starts the stack.
      * @param {!Target} target Target to run thread on.
      * @param {?object} opts optional arguments
-     * @param {?boolean} opts.showVisualReport true if the script should show speech bubble for its value
+     * @param {?boolean} opts.stackClick true if the script was activated by clicking on the stack
      * @param {?boolean} opts.updateMonitor true if the script should update a monitor value
      * @return {!Thread} The newly created thread.
      */
     _pushThread (id, target, opts) {
         opts = Object.assign({
-            showVisualReport: false,
+            stackClick: false,
             updateMonitor: false
         }, opts);
 
         const thread = new Thread(id);
         thread.target = target;
-        thread.showVisualReport = opts.showVisualReport;
+        thread.stackClick = opts.stackClick;
         thread.updateMonitor = opts.updateMonitor;
 
         thread.pushStack(id);
@@ -442,7 +442,7 @@ class Runtime extends EventEmitter {
     _restartThread (thread) {
         const newThread = new Thread(thread.topBlock);
         newThread.target = thread.target;
-        newThread.showVisualReport = thread.showVisualReport;
+        newThread.stackClick = thread.stackClick;
         newThread.updateMonitor = thread.updateMonitor;
         newThread.pushStack(thread.topBlock);
         const i = this.threads.indexOf(thread);
@@ -467,18 +467,28 @@ class Runtime extends EventEmitter {
      * @param {!string} topBlockId ID of block that starts the script.
      * @param {?object} opts optional arguments to toggle script
      * @param {?string} opts.target target ID for target to run script on. If not supplied, uses editing target.
-     * @param {?boolean} opts.showVisualReport true if the speech bubble should pop up on the block, false if not.
      * @param {?boolean} opts.updateMonitor true if the monitor for this block should get updated.
+     * @param {?boolean} opts.stackClick true if the user activated the stack by clicking, false if not. This
+     *     determines whether we show a visual report when turning on the script.
      */
     toggleScript (topBlockId, opts) {
         opts = Object.assign({
             target: this._editingTarget,
-            showVisualReport: false,
-            updateMonitor: false
+            updateMonitor: false,
+            stackClick: false
         }, opts);
         // Remove any existing thread.
         for (let i = 0; i < this.threads.length; i++) {
-            if (this.threads[i].topBlock === topBlockId) {
+            // Toggling a script that's already running turns it off
+            if (this.threads[i].topBlock === topBlockId && this.threads[i].status !== Thread.STATUS_DONE) {
+                const blockContainer = opts.target.blocks;
+                const opcode = blockContainer.getOpcode(blockContainer.getBlock(topBlockId));
+                
+                if (this.getIsEdgeActivatedHat(opcode) && this.threads[i].stackClick !== opts.stackClick) {
+                    // Allow edge activated hat thread stack click to coexist with
+                    // edge activated hat thread that runs every frame
+                    continue;
+                }
                 this._removeThread(this.threads[i]);
                 return;
             }
@@ -577,6 +587,7 @@ class Runtime extends EventEmitter {
                 // any existing threads starting with the top block.
                 for (let i = 0; i < instance.threads.length; i++) {
                     if (instance.threads[i].topBlock === topBlockId &&
+                        !instance.threads[i].stackClick && // stack click threads and hat threads can coexist
                         instance.threads[i].target === target) {
                         instance._restartThread(instance.threads[i]);
                         return;
@@ -587,7 +598,9 @@ class Runtime extends EventEmitter {
                 // give up if any threads with the top block are running.
                 for (let j = 0; j < instance.threads.length; j++) {
                     if (instance.threads[j].topBlock === topBlockId &&
-                        instance.threads[j].target === target) {
+                        instance.threads[j].target === target &&
+                        !instance.threads[j].stackClick && // stack click threads and hat threads can coexist
+                        instance.threads[j].status !== Thread.STATUS_DONE) {
                         // Some thread is already running.
                         return;
                     }
diff --git a/test/fixtures/loudness-hat-block.sb2 b/test/fixtures/loudness-hat-block.sb2
new file mode 100644
index 000000000..3dfe93186
Binary files /dev/null and b/test/fixtures/loudness-hat-block.sb2 differ
diff --git a/test/fixtures/stack-click.sb2 b/test/fixtures/stack-click.sb2
new file mode 100644
index 000000000..8b735b73b
Binary files /dev/null and b/test/fixtures/stack-click.sb2 differ
diff --git a/test/integration/hat-threads-run-every-frame.js b/test/integration/hat-threads-run-every-frame.js
new file mode 100644
index 000000000..4f858c454
--- /dev/null
+++ b/test/integration/hat-threads-run-every-frame.js
@@ -0,0 +1,197 @@
+const path = require('path');
+const test = require('tap').test;
+const makeTestStorage = require('../fixtures/make-test-storage');
+const extract = require('../fixtures/extract');
+const VirtualMachine = require('../../src/index');
+const Thread = require('../../src/engine/thread');
+const Runtime = require('../../src/engine/runtime');
+
+const projectUri = path.resolve(__dirname, '../fixtures/loudness-hat-block.sb2');
+const project = extract(projectUri);
+
+const checkIsHatThread = (t, vm, hatThread) => {
+    t.equal(hatThread.stackClick, false);
+    t.equal(hatThread.updateMonitor, false);
+    const blockContainer = hatThread.target.blocks;
+    const opcode = blockContainer.getOpcode(blockContainer.getBlock(hatThread.topBlock));
+    t.assert(vm.runtime.getIsEdgeActivatedHat(opcode));
+};
+
+const checkIsStackClickThread = (t, vm, stackClickThread) => {
+    t.equal(stackClickThread.stackClick, true);
+    t.equal(stackClickThread.updateMonitor, false);
+};
+
+/**
+ * loudness-hat-block.sb2 contains a single stack
+ *     when loudness > 10
+ *         change color effect by 25
+ * The intention is to make sure that the hat block condition is evaluated
+ * on each frame.
+ */
+test('edge activated hat thread runs once every frame', t => {
+    const vm = new VirtualMachine();
+    vm.attachStorage(makeTestStorage());
+
+    // Start VM, load project, and run
+    t.doesNotThrow(() => {
+        // Note: don't run vm.start(), we handle calling _step() manually in this test
+        vm.runtime.currentStepTime = Runtime.THREAD_STEP_INTERVAL;
+        vm.clear();
+        vm.setCompatibilityMode(false);
+        vm.setTurboMode(false);
+
+        vm.loadProject(project).then(() => {
+            t.equal(vm.runtime.threads.length, 0);
+
+            vm.runtime._step();
+            t.equal(vm.runtime.threads.length, 1);
+            checkIsHatThread(t, vm, vm.runtime.threads[0]);
+            t.assert(vm.runtime.threads[0].status === Thread.STATUS_DONE);
+
+            // Check that the hat thread is again when another step is taken
+            vm.runtime._step();
+            // There should now be one done hat thread and one new hat thread to run
+            t.equal(vm.runtime.threads.length, 1);
+            checkIsHatThread(t, vm, vm.runtime.threads[0]);
+            t.assert(vm.runtime.threads[0].status === Thread.STATUS_DONE);
+            t.end();
+        });
+    });
+});
+
+/**
+ * If the hat doesn't finish evaluating within one frame, it shouldn't be added again
+ * on the next frame. (We skip execution by setting the step time to 0)
+ */
+test('edge activated hat thread not added twice', t => {
+    const vm = new VirtualMachine();
+    vm.attachStorage(makeTestStorage());
+
+    // Start VM, load project, and run
+    t.doesNotThrow(() => {
+        // Note: don't run vm.start(), we handle calling _step() manually in this test
+        vm.runtime.currentStepTime = 0;
+        vm.clear();
+        vm.setCompatibilityMode(false);
+        vm.setTurboMode(false);
+
+        vm.loadProject(project).then(() => {
+            t.equal(vm.runtime.threads.length, 0);
+
+            vm.runtime._step();
+            t.equal(vm.runtime.threads.length, 1);
+            const prevThread = vm.runtime.threads[0];
+            checkIsHatThread(t, vm, vm.runtime.threads[0]);
+            t.assert(vm.runtime.threads[0].status === Thread.STATUS_RUNNING);
+
+            // Check that no new threads are added when another step is taken
+            vm.runtime._step();
+            // There should now be one done hat thread and one new hat thread to run
+            t.equal(vm.runtime.threads.length, 1);
+            checkIsHatThread(t, vm, vm.runtime.threads[0]);
+            t.assert(vm.runtime.threads[0] === prevThread);
+            t.end();
+        });
+    });
+});
+
+/**
+ * When adding a stack click thread first, make sure that the edge activated hat thread and
+ * the stack click thread are both pushed and run (despite having the same top block)
+ */
+test('edge activated hat thread does not interrupt stack click thread', t => {
+    const vm = new VirtualMachine();
+    vm.attachStorage(makeTestStorage());
+
+    // Start VM, load project, and run
+    t.doesNotThrow(() => {
+        // Note: don't run vm.start(), we handle calling _step() manually in this test
+        vm.runtime.currentStepTime = Runtime.THREAD_STEP_INTERVAL;
+        vm.clear();
+        vm.setCompatibilityMode(false);
+        vm.setTurboMode(false);
+
+        vm.loadProject(project).then(() => {
+            t.equal(vm.runtime.threads.length, 0);
+
+            vm.runtime._step();
+            t.equal(vm.runtime.threads.length, 1);
+            checkIsHatThread(t, vm, vm.runtime.threads[0]);
+            t.assert(vm.runtime.threads[0].status === Thread.STATUS_DONE);
+
+            // Add stack click thread on this hat
+            vm.runtime.toggleScript(vm.runtime.threads[0].topBlock, {stackClick: true});
+
+            // Check that the hat thread is again when another step is taken
+            vm.runtime._step();
+            // There should now be one done hat thread and one new hat thread to run
+            t.equal(vm.runtime.threads.length, 2);
+            let hatThread;
+            let stackClickThread;
+            if (vm.runtime.threads[0].stackClick) {
+                stackClickThread = vm.runtime.threads[0];
+                hatThread = vm.runtime.threads[1];
+            } else {
+                stackClickThread = vm.runtime.threads[1];
+                hatThread = vm.runtime.threads[0];
+            }
+            checkIsHatThread(t, vm, hatThread);
+            checkIsStackClickThread(t, vm, stackClickThread);
+            t.assert(vm.runtime.threads[0].status === Thread.STATUS_DONE);
+            t.assert(vm.runtime.threads[1].status === Thread.STATUS_DONE);
+            t.end();
+        });
+    });
+});
+
+/**
+ * When adding the hat thread first, make sure that the edge activated hat thread and
+ * the stack click thread are both pushed and run (despite having the same top block)
+ */
+test('edge activated hat thread does not interrupt stack click thread', t => {
+    const vm = new VirtualMachine();
+    vm.attachStorage(makeTestStorage());
+
+    // Start VM, load project, and run
+    t.doesNotThrow(() => {
+        // Note: don't run vm.start(), we handle calling _step() manually in this test
+        vm.runtime.currentStepTime = 0;
+        vm.clear();
+        vm.setCompatibilityMode(false);
+        vm.setTurboMode(false);
+
+        vm.loadProject(project).then(() => {
+            t.equal(vm.runtime.threads.length, 0);
+
+            vm.runtime._step();
+            t.equal(vm.runtime.threads.length, 1);
+            checkIsHatThread(t, vm, vm.runtime.threads[0]);
+            t.assert(vm.runtime.threads[0].status === Thread.STATUS_RUNNING);
+
+            vm.runtime.currentStepTime = Runtime.THREAD_STEP_INTERVAL;
+
+            // Add stack click thread on this hat
+            vm.runtime.toggleScript(vm.runtime.threads[0].topBlock, {stackClick: true});
+
+            // Check that the hat thread is again when another step is taken
+            vm.runtime._step();
+            // There should now be one done hat thread and one new hat thread to run
+            t.equal(vm.runtime.threads.length, 2);
+            let hatThread;
+            let stackClickThread;
+            if (vm.runtime.threads[0].stackClick) {
+                stackClickThread = vm.runtime.threads[0];
+                hatThread = vm.runtime.threads[1];
+            } else {
+                stackClickThread = vm.runtime.threads[1];
+                hatThread = vm.runtime.threads[0];
+            }
+            checkIsHatThread(t, vm, hatThread);
+            checkIsStackClickThread(t, vm, stackClickThread);
+            t.assert(vm.runtime.threads[0].status === Thread.STATUS_DONE);
+            t.assert(vm.runtime.threads[1].status === Thread.STATUS_DONE);
+            t.end();
+        });
+    });
+});
diff --git a/test/integration/stack-click.js b/test/integration/stack-click.js
new file mode 100644
index 000000000..1243eed6a
--- /dev/null
+++ b/test/integration/stack-click.js
@@ -0,0 +1,59 @@
+const path = require('path');
+const test = require('tap').test;
+const makeTestStorage = require('../fixtures/make-test-storage');
+const extract = require('../fixtures/extract');
+const VirtualMachine = require('../../src/index');
+
+const projectUri = path.resolve(__dirname, '../fixtures/stack-click.sb2');
+const project = extract(projectUri);
+
+/**
+ * stack-click.sb2 contains a sprite at (0, 0) with a single stack
+ *     when timer > 100000000
+ *         move 100 steps
+ * The intention is to make sure that the stack can be activated by a stack click
+ * even when the hat predicate is false.
+ */
+test('stack click activates the stack', t => {
+    const vm = new VirtualMachine();
+    vm.attachStorage(makeTestStorage());
+
+    // Evaluate playground data and exit
+    vm.on('playgroundData', () => {
+        // The sprite should have moved 100 to the right
+        t.equal(vm.editingTarget.x, 100);
+        t.end();
+        process.nextTick(process.exit);
+    });
+
+    // Start VM, load project, and run
+    t.doesNotThrow(() => {
+        vm.start();
+        vm.clear();
+        vm.setCompatibilityMode(false);
+        vm.setTurboMode(false);
+        vm.loadProject(project).then(() => {
+            const blockContainer = vm.runtime.targets[1].blocks;
+            const allBlocks = blockContainer._blocks;
+
+            // Confirm the editing target is initially at 0
+            t.equal(vm.editingTarget.x, 0);
+
+            // Find hat for greater than and click it
+            for (const blockId in allBlocks) {
+                if (allBlocks[blockId].opcode === 'event_whengreaterthan') {
+                    blockContainer.blocklyListen({
+                        blockId: blockId,
+                        element: 'stackclick'
+                    }, vm.runtime);
+                }
+            }
+
+            // After two seconds, get playground data and stop
+            setTimeout(() => {
+                vm.getPlaygroundData();
+                vm.stopAll();
+            }, 2000);
+        });
+    });
+});