diff --git a/app/assets/javascripts/workers/worker_world.js b/app/assets/javascripts/workers/worker_world.js
index c7a6ee560..2ec1d73f8 100644
--- a/app/assets/javascripts/workers/worker_world.js
+++ b/app/assets/javascripts/workers/worker_world.js
@@ -464,6 +464,11 @@ self.addFlagEvent = function addFlagEvent(flagEvent) {
   self.world.addFlagEvent(flagEvent);
 };
 
+self.stopRealTimePlayback = function stopRealTimePlayback() {
+  if(!self.world) return;
+  self.world.realTime = false;
+};
+
 self.addEventListener('message', function(event) {
   self[event.data.func](event.data.args);
 });
diff --git a/app/lib/Angel.coffee b/app/lib/Angel.coffee
index 59547ca16..3e7a8efce 100644
--- a/app/lib/Angel.coffee
+++ b/app/lib/Angel.coffee
@@ -14,6 +14,7 @@ module.exports = class Angel extends CocoClass
 
   subscriptions:
     'level:flag-updated': 'onFlagEvent'
+    'playback:stop-real-time-playback': 'onStopRealTimePlayback'
 
   constructor: (@shared) ->
     super()
@@ -212,6 +213,10 @@ module.exports = class Angel extends CocoClass
     return unless @running and @work.realTime
     @worker.postMessage func: 'addFlagEvent', args: e
 
+  onStopRealTimePlayback: (e) ->
+    return unless @running and @work.realTime
+    @work.realTime = false
+    @worker.postMessage func: 'stopRealTimePlayback'
 
   #### Synchronous code for running worlds on main thread (profiling / IE9) ####
   simulateSync: (work) =>
diff --git a/app/lib/God.coffee b/app/lib/God.coffee
index de1f6b685..8d1b90228 100644
--- a/app/lib/God.coffee
+++ b/app/lib/God.coffee
@@ -65,7 +65,7 @@ module.exports = class God extends CocoClass
       isPreloading = angel.running and angel.work.preload and _.isEqual angel.work.userCodeMap, userCodeMap, (a, b) ->
         return a.raw is b.raw if a?.raw? and b?.raw?
         undefined  # Let default equality test suffice.
-      if not hadPreloader and isPreloading
+      if not hadPreloader and isPreloading and not realTime
         angel.finalizePreload()
         hadPreloader = true
       else if preload and angel.running and not angel.work.preload
diff --git a/app/lib/surface/Surface.coffee b/app/lib/surface/Surface.coffee
index 92715bd5a..013c6801f 100644
--- a/app/lib/surface/Surface.coffee
+++ b/app/lib/surface/Surface.coffee
@@ -8,6 +8,7 @@ CameraBorder = require './CameraBorder'
 Layer = require './Layer'
 Letterbox = require './Letterbox'
 Dimmer = require './Dimmer'
+CountdownScreen = require './CountdownScreen'
 PlaybackOverScreen = require './PlaybackOverScreen'
 DebugDisplay = require './DebugDisplay'
 CoordinateDisplay = require './CoordinateDisplay'
@@ -100,6 +101,7 @@ module.exports = Surface = class Surface extends CocoClass
     @spriteBoss.destroy()
     @chooser?.destroy()
     @dimmer?.destroy()
+    @countdownScreen?.destroy()
     @playbackOverScreen?.destroy()
     @stage.clear()
     @musicPlayer?.destroy()
@@ -402,6 +404,7 @@ module.exports = Surface = class Surface extends CocoClass
     @surfaceLayer.addChild @cameraBorder = new CameraBorder bounds: @camera.bounds
     @screenLayer.addChild new Letterbox canvasWidth: canvasWidth, canvasHeight: canvasHeight
     @spriteBoss = new SpriteBoss camera: @camera, surfaceLayer: @surfaceLayer, surfaceTextLayer: @surfaceTextLayer, world: @world, thangTypes: @options.thangTypes, choosing: @options.choosing, navigateToSelection: @options.navigateToSelection, showInvisible: @options.showInvisible
+    @countdownScreen ?= new CountdownScreen camera: @camera, layer: @screenLayer
     @playbackOverScreen ?= new PlaybackOverScreen camera: @camera, layer: @screenLayer
     @stage.enableMouseOver(10)
     @stage.addEventListener 'stagemousemove', @onMouseMove
@@ -414,6 +417,7 @@ module.exports = Surface = class Surface extends CocoClass
     @onResize()
 
   onResize: (e) =>
+    return if @destroyed
     oldWidth = parseInt @canvas.attr('width'), 10
     oldHeight = parseInt @canvas.attr('height'), 10
     aspectRatio = oldWidth / oldHeight
@@ -630,6 +634,7 @@ module.exports = Surface = class Surface extends CocoClass
     @realTime = true
     @onResize()
     @spriteBoss.selfWizardSprite?.toggle false
+    @playing = false  # Will start when countdown is done.
 
   onRealTimePlaybackEnded: (e) ->
     @realTime = false
diff --git a/app/lib/world/world.coffee b/app/lib/world/world.coffee
index e76ab2707..629a25f63 100644
--- a/app/lib/world/world.coffee
+++ b/app/lib/world/world.coffee
@@ -15,6 +15,7 @@ DESERIALIZATION_INTERVAL = 10
 REAL_TIME_BUFFER_MIN = 2 * PROGRESS_UPDATE_INTERVAL
 REAL_TIME_BUFFER_MAX = 3 * PROGRESS_UPDATE_INTERVAL
 REAL_TIME_BUFFERED_WAIT_INTERVAL = 0.5 * PROGRESS_UPDATE_INTERVAL
+REAL_TIME_COUNTDOWN_DELAY = 3000  # match CountdownScreen
 ITEM_ORIGINAL = '53e12043b82921000051cdf9'
 
 module.exports = class World
@@ -93,12 +94,14 @@ module.exports = class World
   loadFrames: (loadedCallback, errorCallback, loadProgressCallback, skipDeferredLoading, loadUntilFrame) ->
     return if @aborted
     console.log 'Warning: loadFrames called on empty World (no thangs).' unless @thangs.length
+    continueLaterFn = =>
+      @loadFrames(loadedCallback, errorCallback, loadProgressCallback, skipDeferredLoading, loadUntilFrame) unless @destroyed
+    if @realTime and not @countdownFinished
+      return setTimeout @finishCountdown(continueLaterFn), REAL_TIME_COUNTDOWN_DELAY
     t1 = now()
     @t0 ?= t1
     @worldLoadStartTime ?= t1
     @lastRealTimeUpdate ?= 0
-    continueLaterFn = =>
-      @loadFrames(loadedCallback, errorCallback, loadProgressCallback, skipDeferredLoading, loadUntilFrame) unless @destroyed
     frameToLoadUntil = if loadUntilFrame then loadUntilFrame + 1 else @totalFrames  # Might stop early if debugging.
     i = @frames.length
     while i < frameToLoadUntil and i < @totalFrames
@@ -123,6 +126,11 @@ module.exports = class World
       loadProgressCallback? 1
       loadedCallback()
 
+  finishCountdown: (continueLaterFn) -> =>
+    return if @destroyed
+    @countdownFinished = true
+    continueLaterFn()
+
   shouldDelayRealTimeSimulation: (t) ->
     return false unless @realTime
     timeSinceStart = t - @worldLoadStartTime
diff --git a/app/locale/en.coffee b/app/locale/en.coffee
index 27c340771..a6f87da93 100644
--- a/app/locale/en.coffee
+++ b/app/locale/en.coffee
@@ -358,6 +358,7 @@
     grid: "Grid"
     customize_wizard: "Customize Wizard"
     home: "Home"
+    stop: "Stop"
     game_menu: "Game Menu"
     guide: "Guide"
     restart: "Restart"
diff --git a/app/schemas/subscriptions/play.coffee b/app/schemas/subscriptions/play.coffee
index f9dce2648..c73370473 100644
--- a/app/schemas/subscriptions/play.coffee
+++ b/app/schemas/subscriptions/play.coffee
@@ -147,6 +147,10 @@ module.exports =
   'playback:manually-scrubbed':
     {} # TODO schema
 
+  'playback:stop-real-time-playback':
+    type: 'object'
+    additionalProperties: false
+
   'playback:real-time-playback-started':
     type: 'object'
     additionalProperties: false
diff --git a/app/styles/play/level.sass b/app/styles/play/level.sass
index d2fa31a62..524b01db8 100644
--- a/app/styles/play/level.sass
+++ b/app/styles/play/level.sass
@@ -19,6 +19,10 @@ body.is-playing
         margin: 0 auto
     #control-bar-view
       width: 100%
+      button, h4
+        display: none
+      #stop-real-time-playback-button
+        display: block
     #playback-view
       $flags-width: 200px
       width: 90%
diff --git a/app/styles/play/level/control_bar.sass b/app/styles/play/level/control_bar.sass
index ad5c89a36..d8828f4c6 100644
--- a/app/styles/play/level/control_bar.sass
+++ b/app/styles/play/level/control_bar.sass
@@ -41,5 +41,5 @@
     height: 24px
 
 
-  #level-done-button
+  #level-done-button, #stop-real-time-playback-button
     display: none
diff --git a/app/templates/play/level/control_bar.jade b/app/templates/play/level/control_bar.jade
index 436c177a0..d1d2eeda8 100644
--- a/app/templates/play/level/control_bar.jade
+++ b/app/templates/play/level/control_bar.jade
@@ -9,6 +9,9 @@ h4.title
   |  - 
   a(href=editorLink, data-i18n="nav.editor", title="Open " + worldName + " in the Level Editor") Editor
 
+
+button.btn.btn-xs.btn-warning.banner#stop-real-time-playback-button(title="Stop real-time playback", data-i18n="play_level.stop") Stop
+
 button.btn.btn-xs.btn-inverse.banner#game-menu-button(title="Show game menu", data-i18n="play_level.game_menu") Game Menu
 
 button.btn.btn-xs.btn-success.banner#docs-button(title="Show level instructions", data-i18n="play_level.guide") Guide
diff --git a/app/views/play/level/ControlBarView.coffee b/app/views/play/level/ControlBarView.coffee
index 1d07c745c..d4fbe91c6 100644
--- a/app/views/play/level/ControlBarView.coffee
+++ b/app/views/play/level/ControlBarView.coffee
@@ -16,13 +16,13 @@ module.exports = class ControlBarView extends CocoView
       window.tracker?.trackEvent 'Clicked Docs', level: @level.get('name'), label: @level.get('name')
       @showGuideModal()
 
-    'click #next-game-button': ->
-      Backbone.Mediator.publish 'next-game-pressed'
+    'click #next-game-button': -> Backbone.Mediator.publish 'next-game-pressed', {}
 
-    'click #game-menu-button': ->
-      @showGameMenuModal()
+    'click #game-menu-button': 'showGameMenuModal'
 
-    'click': -> Backbone.Mediator.publish 'tome:focus-editor'
+    'click #stop-real-time-playback-button': -> Backbone.Mediator.publish 'playback:stop-real-time-playback', {}
+
+    'click': -> Backbone.Mediator.publish 'tome:focus-editor', {}
 
   constructor: (options) ->
     @worldName = options.worldName
diff --git a/app/views/play/level/LevelPlaybackView.coffee b/app/views/play/level/LevelPlaybackView.coffee
index 63fdc9379..b783d3db4 100644
--- a/app/views/play/level/LevelPlaybackView.coffee
+++ b/app/views/play/level/LevelPlaybackView.coffee
@@ -26,6 +26,7 @@ module.exports = class LevelPlaybackView extends CocoView
     'level-set-letterbox': 'onSetLetterbox'
     'tome:cast-spells': 'onTomeCast'
     'playback:real-time-playback-ended': 'onRealTimePlaybackEnded'
+    'playback:stop-real-time-playback': 'onStopRealTimePlayback'
 
   events:
     'click #debug-toggle': 'onToggleDebug'
@@ -34,11 +35,11 @@ module.exports = class LevelPlaybackView extends CocoView
     'click #edit-editor-config': 'onEditEditorConfig'
     'click #view-keyboard-shortcuts': 'onViewKeyboardShortcuts'
     'click #music-button': 'onToggleMusic'
-    'click #zoom-in-button': -> Backbone.Mediator.publish('camera-zoom-in') unless @shouldIgnore()
-    'click #zoom-out-button': -> Backbone.Mediator.publish('camera-zoom-out') unless @shouldIgnore()
+    'click #zoom-in-button': -> Backbone.Mediator.publish('camera-zoom-in', {}) unless @shouldIgnore()
+    'click #zoom-out-button': -> Backbone.Mediator.publish('camera-zoom-out', {}) unless @shouldIgnore()
     'click #volume-button': 'onToggleVolume'
     'click #play-button': 'onTogglePlay'
-    'click': -> Backbone.Mediator.publish 'tome:focus-editor' unless @realTime
+    'click': -> Backbone.Mediator.publish 'tome:focus-editor', {} unless @realTime
     'mouseenter #timeProgress': 'onProgressEnter'
     'mouseleave #timeProgress': 'onProgressLeave'
     'mousemove #timeProgress': 'onProgressHover'
@@ -308,6 +309,9 @@ module.exports = class LevelPlaybackView extends CocoView
     @realTime = false
     @togglePlaybackControls true
 
+  onStopRealTimePlayback: (e) ->
+    Backbone.Mediator.publish 'playback:real-time-playback-ended', {}
+
   onSetDebug: (e) ->
     flag = $('#debug-toggle i.icon-ok')
     flag.toggleClass 'invisible', not e.debug