diff --git a/app/lib/surface/CoordinateDisplay.coffee b/app/lib/surface/CoordinateDisplay.coffee
index 155f7e7ac..8d235cec9 100644
--- a/app/lib/surface/CoordinateDisplay.coffee
+++ b/app/lib/surface/CoordinateDisplay.coffee
@@ -11,7 +11,9 @@ module.exports = class CoordinateDisplay extends createjs.Container
     super()
     @initialize()
     @camera = options.camera
-    console.error 'CoordinateDisplay needs camera.' unless @camera
+    @layer = options.layer
+    console.error @toString(), 'needs a camera.' unless @camera
+    console.error @toString(), 'needs a layer.' unless @layer
     @build()
     @show = _.debounce @show, 125
     Backbone.Mediator.subscribe(channel, @[func], @) for channel, func of @subscriptions
@@ -21,6 +23,8 @@ module.exports = class CoordinateDisplay extends createjs.Container
     @show = null
     @destroyed = true
 
+  toString: -> '<CoordinateDisplay>'
+
   build: ->
     @mouseEnabled = @mouseChildren = false
     @addChild @background = new createjs.Shape()
@@ -30,6 +34,7 @@ module.exports = class CoordinateDisplay extends createjs.Container
     @label.shadow = new createjs.Shadow('#000000', 1, 1, 0)
     @background.name = 'Coordinate Display Background'
     @pointMarker.name = 'Point Marker'
+    @layer.addChild @
 
   onMouseOver: (e) -> @mouseInBounds = true
   onMouseOut: (e) -> @mouseInBounds = false
diff --git a/app/lib/surface/CoordinateGrid.coffee b/app/lib/surface/CoordinateGrid.coffee
new file mode 100644
index 000000000..7535ccf6d
--- /dev/null
+++ b/app/lib/surface/CoordinateGrid.coffee
@@ -0,0 +1,102 @@
+CocoClass = require 'lib/CocoClass'
+
+module.exports = class CoordinateGrid extends CocoClass
+  subscriptions:
+    'level-toggle-grid': 'onToggleGrid'
+    'level-set-grid': 'onSetGrid'
+
+  shortcuts:
+    'ctrl+g, ⌘+g': 'onToggleGrid'
+
+  constructor: (options, worldSize) ->
+    super()
+    options ?= {}
+    @camera = options.camera
+    @layer = options.layer
+    @textLayer = options.textLayer
+    console.error @toString(), 'needs a camera.' unless @camera
+    console.error @toString(), 'needs a layer.' unless @layer
+    console.error @toString(), 'needs a textLayer.' unless @textLayer
+    @build worldSize
+
+  destroy: ->
+    super()
+
+  toString: -> '<CoordinateGrid>'
+
+  build: (worldSize) ->
+    worldWidth = worldSize[0] ? 80
+    worldHeight = worldSize[1] ? 68
+    @gridContainer = new createjs.Container()
+    @gridShape = new createjs.Shape()
+    @gridContainer.addChild @gridShape
+    @gridContainer.mouseEnabled = false
+    @gridShape.alpha = 0.125
+    @gridShape.graphics.setStrokeStyle 1
+    @gridShape.graphics.beginStroke 'blue'
+    gridSize = Math.round(worldWidth / 20)
+    wopStart = x: 0, y: 0
+    wopEnd = x: worldWidth, y: worldHeight
+    supStart = @camera.worldToSurface wopStart
+    supEnd = @camera.worldToSurface wopEnd
+    wop = x: wopStart.x, y: wopStart.y
+    @labels = []
+    linesDrawn = 0
+    while wop.x <= wopEnd.x
+      sup = @camera.worldToSurface wop
+      @gridShape.graphics.mt(sup.x, supStart.y).lt(sup.x, supEnd.y)
+      if ++linesDrawn % 2
+        t = new createjs.Text(wop.x.toFixed(0), '16px Arial', 'blue')
+        t.textAlign = 'center'
+        t.textBaseline = 'bottom'
+        t.x = sup.x
+        t.y = supStart.y
+        t.alpha = 0.75
+        @labels.push t
+      wop.x += gridSize
+      if wopEnd.x < wop.x <= wopEnd.x - gridSize / 2
+        wop.x = wopEnd.x
+    linesDrawn = 0
+    while wop.y <= wopEnd.y
+      sup = @camera.worldToSurface wop
+      @gridShape.graphics.mt(supStart.x, sup.y).lt(supEnd.x, sup.y)
+      if ++linesDrawn % 2
+        t = new createjs.Text(wop.y.toFixed(0), '16px Arial', 'blue')
+        t.textAlign = 'left'
+        t.textBaseline = 'middle'
+        t.x = 0
+        t.y = sup.y
+        t.alpha = 0.75
+        @labels.push t
+      wop.y += gridSize
+      console.log wop.y, wopEnd.y, gridSize
+      if wopEnd.y < wop.y <= wopEnd.y - gridSize / 2
+        wop.y = wopEnd.y
+    @gridShape.graphics.endStroke()
+    bounds = x: supStart.x, y: supEnd.y, width: supEnd.x - supStart.x, height: supStart.y - supEnd.y
+    return unless bounds?.width and bounds.height
+    @gridContainer.cache bounds.x, bounds.y, bounds.width, bounds.height
+
+  showGrid: ->
+    return if @gridShowing()
+    @layer.addChild @gridContainer
+    @textLayer.addChild label for label in @labels
+
+  hideGrid: ->
+    return unless @gridShowing()
+    @layer.removeChild @gridContainer
+    @textLayer.removeChild label for label in @labels
+
+  gridShowing: ->
+    @gridContainer?.parent?
+
+  onToggleGrid: (e) ->
+    # TODO: figure out a better way of managing grid / debug so it's not split across PlaybackView and Surface
+    e?.preventDefault?()
+    if @gridShowing() then @hideGrid() else @showGrid()
+    flag = $('#grid-toggle i.icon-ok')
+    flag.toggleClass 'invisible', not @gridShowing()
+
+  onSetGrid: (e) ->
+    if e.grid then @showGrid() else @hideGrid()
+
diff --git a/app/lib/surface/Surface.coffee b/app/lib/surface/Surface.coffee
index 013c6801f..9ba6ab0b6 100644
--- a/app/lib/surface/Surface.coffee
+++ b/app/lib/surface/Surface.coffee
@@ -12,6 +12,7 @@ CountdownScreen = require './CountdownScreen'
 PlaybackOverScreen = require './PlaybackOverScreen'
 DebugDisplay = require './DebugDisplay'
 CoordinateDisplay = require './CoordinateDisplay'
+CoordinateGrid = require './CoordinateGrid'
 SpriteBoss = require './SpriteBoss'
 PointChooser = require './PointChooser'
 RegionChooser = require './RegionChooser'
@@ -24,7 +25,7 @@ module.exports = Surface = class Surface extends CocoClass
   surfaceLayer: null
   surfaceTextLayer: null
   screenLayer: null
-  gridLayer: null  # TODO: maybe
+  gridLayer: null
 
   spriteBoss: null
 
@@ -56,8 +57,6 @@ module.exports = Surface = class Surface extends CocoClass
     'level-set-playing': 'onSetPlaying'
     'level-set-debug': 'onSetDebug'
     'level-toggle-debug': 'onToggleDebug'
-    'level-set-grid': 'onSetGrid'
-    'level-toggle-grid': 'onToggleGrid'
     'level-toggle-pathfinding': 'onTogglePathFinding'
     'level-set-time': 'onSetTime'
     'level-set-surface-camera': 'onSetCamera'
@@ -75,7 +74,6 @@ module.exports = Surface = class Surface extends CocoClass
 
   shortcuts:
     'ctrl+\\, ⌘+\\': 'onToggleDebug'
-    'ctrl+g, ⌘+g': 'onToggleGrid'
     'ctrl+o, ⌘+o': 'onTogglePathFinding'
 
   # external functions
@@ -103,6 +101,8 @@ module.exports = Surface = class Surface extends CocoClass
     @dimmer?.destroy()
     @countdownScreen?.destroy()
     @playbackOverScreen?.destroy()
+    @coordinateDisplay?.destroy()
+    @coordinateGrid?.destroy()
     @stage.clear()
     @musicPlayer?.destroy()
     @stage.removeAllChildren()
@@ -126,10 +126,6 @@ module.exports = Surface = class Surface extends CocoClass
 
     @showLevel()
     @updateState true if @loaded
-    # TODO: synchronize both ways of choosing whether to show coords (@world via UI System or @options via World Select modal)
-    if @world.showCoordinates and @options.coords and not @coordinateDisplay
-      @coordinateDisplay = new CoordinateDisplay camera: @camera
-      @surfaceTextLayer.addChild @coordinateDisplay
     @onFrameChanged()
     Backbone.Mediator.publish 'surface:world-set-up'
 
@@ -364,6 +360,9 @@ module.exports = Surface = class Surface extends CocoClass
 
   onNewWorld: (event) ->
     return unless event.world.name is @world.name
+    @onStreamingWorldUpdated event
+
+  onStreamingWorldUpdated: (event) ->
     @casting = false
     @spriteBoss.play()
 
@@ -390,22 +389,21 @@ module.exports = Surface = class Surface extends CocoClass
   # initialization
 
   initEasel: ->
-    # takes DOM objects, not jQuery objects
-    @stage = new createjs.Stage(@canvas[0])
+    @stage = new createjs.Stage(@canvas[0])  # Takes DOM objects, not jQuery objects.
     canvasWidth = parseInt @canvas.attr('width'), 10
     canvasHeight = parseInt @canvas.attr('height'), 10
-    @camera?.destroy()
-    @camera = new Camera @canvas
-    AudioPlayer.camera = @camera
+    @camera = AudioPlayer.camera = new Camera @canvas
     @layers.push @surfaceLayer = new Layer name: 'Surface', layerPriority: 0, transform: Layer.TRANSFORM_SURFACE, camera: @camera
     @layers.push @surfaceTextLayer = new Layer name: 'Surface Text', layerPriority: 1, transform: Layer.TRANSFORM_SURFACE_TEXT, camera: @camera
-    @layers.push @screenLayer = new Layer name: 'Screen', layerPriority: 2, transform: Layer.TRANSFORM_SCREEN, camera: @camera
+    @layers.push @gridLayer = new Layer name: 'Grid', layerPriority: 2, transform: Layer.TRANSFORM_SURFACE, camera: @camera
+    @layers.push @screenLayer = new Layer name: 'Screen', layerPriority: 3, transform: Layer.TRANSFORM_SCREEN, camera: @camera
     @stage.addChild @layers...
     @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
+    @countdownScreen = new CountdownScreen camera: @camera, layer: @screenLayer
+    @playbackOverScreen = new PlaybackOverScreen camera: @camera, layer: @screenLayer
+    @initCoordinates()
     @stage.enableMouseOver(10)
     @stage.addEventListener 'stagemousemove', @onMouseMove
     @stage.addEventListener 'stagemousedown', @onMouseDown
@@ -416,6 +414,11 @@ module.exports = Surface = class Surface extends CocoClass
     createjs.Ticker.setFPS @options.frameRate
     @onResize()
 
+  initCoordinates: ->
+    @coordinateGrid ?= new CoordinateGrid {camera: @camera, layer: @gridLayer, textLayer: @surfaceTextLayer}, @world.size()
+    @coordinateGrid.showGrid() if @world.showGrid or @options.grid
+    @coordinateDisplay ?= new CoordinateDisplay camera: @camera, layer: @surfaceTextLayer if @world.showCoordinates or @options.coords
+
   onResize: (e) =>
     return if @destroyed
     oldWidth = parseInt @canvas.attr('width'), 10
@@ -450,7 +453,6 @@ module.exports = Surface = class Surface extends CocoClass
     Backbone.Mediator.publish 'registrar-echo-states'
     @updateState true
     @drawCurrentFrame()
-    @showGrid() if @options.grid  # TODO: pay attention to world grid setting (which we only know when world simulates)
     createjs.Ticker.addEventListener 'tick', @tick
     Backbone.Mediator.publish 'level:started'
 
@@ -460,67 +462,6 @@ module.exports = Surface = class Surface extends CocoClass
   initAudio: ->
     @musicPlayer = new MusicPlayer()
 
-  # grid; should probably refactor into separate class
-
-  showGrid: ->
-    return if @gridShowing()
-    unless @gridLayer
-      @gridLayer = new createjs.Container()
-      @gridShape = new createjs.Shape()
-      @gridLayer.addChild @gridShape
-      @gridLayer.z = 90019001
-      @gridLayer.mouseEnabled = false
-      @gridShape.alpha = 0.125
-      @gridShape.graphics.beginStroke 'blue'
-      gridSize = Math.round(@world.size()[0] / 20)
-      unless gridSize > 0.1
-        return console.error 'Grid size is', gridSize, 'so we can\'t draw a grid.'
-      wopStart = x: 0, y: 0
-      wopEnd = x: @world.size()[0], y: @world.size()[1]
-      supStart = @camera.worldToSurface wopStart
-      supEnd = @camera.worldToSurface wopEnd
-      wop = x: wopStart.x, y: wopStart.y
-      while wop.x < wopEnd.x
-        sup = @camera.worldToSurface wop
-        @gridShape.graphics.mt(sup.x, supStart.y).lt(sup.x, supEnd.y)
-        t = new createjs.Text(wop.x.toFixed(0), '16px Arial', 'blue')
-        t.x = sup.x - t.getMeasuredWidth() / 2
-        t.y = supStart.y - 10 - t.getMeasuredHeight() / 2
-        t.alpha = 0.75
-        @gridLayer.addChild t
-        wop.x += gridSize
-      while wop.y < wopEnd.y
-        sup = @camera.worldToSurface wop
-        @gridShape.graphics.mt(supStart.x, sup.y).lt(supEnd.x, sup.y)
-        t = new createjs.Text(wop.y.toFixed(0), '16px Arial', 'blue')
-        t.x = 10 - t.getMeasuredWidth() / 2
-        t.y = sup.y - t.getMeasuredHeight() / 2
-        t.alpha = 0.75
-        @gridLayer.addChild t
-        wop.y += gridSize
-      @gridShape.graphics.endStroke()
-      bounds = @gridLayer.getBounds()
-      return unless bounds?.width and bounds.height
-      @gridLayer.cache bounds.x, bounds.y, bounds.width, bounds.height
-    @surfaceLayer.addChild @gridLayer
-
-  hideGrid: ->
-    return unless @gridShowing()
-    @gridLayer.parent.removeChild @gridLayer
-
-  gridShowing: ->
-    @gridLayer?.parent?
-
-  onToggleGrid: (e) ->
-    # TODO: figure out a better way of managing grid / debug so it's not split across PlaybackView and Surface
-    e?.preventDefault?()
-    if @gridShowing() then @hideGrid() else @showGrid()
-    flag = $('#grid-toggle i.icon-ok')
-    flag.toggleClass 'invisible', not @gridShowing()
-
-  onSetGrid: (e) ->
-    if e.grid then @showGrid() else @hideGrid()
-
   onToggleDebug: (e) ->
     e?.preventDefault?()
     Backbone.Mediator.publish 'level-set-debug', {debug: not @debug}
diff --git a/app/locale/en.coffee b/app/locale/en.coffee
index ee033e20d..fc7fe48b5 100644
--- a/app/locale/en.coffee
+++ b/app/locale/en.coffee
@@ -355,7 +355,6 @@
 
   play_level:
     done: "Done"
-    grid: "Grid"
     customize_wizard: "Customize Wizard"
     home: "Home"
     stop: "Stop"
diff --git a/app/templates/play/level/playback.jade b/app/templates/play/level/playback.jade
index afcbd8276..f0c83859b 100644
--- a/app/templates/play/level/playback.jade
+++ b/app/templates/play/level/playback.jade
@@ -37,10 +37,6 @@ button.btn.btn-xs.btn-inverse#music-button(title="Toggle Music")
     li.selectable#view-keyboard-shortcuts
       i.icon-info-sign
       span(data-i18n="play_level.keyboard_shortcuts") Key Shortcuts
-    li(title="Ctrl/Cmd + G: Toggle grid display").selectable#grid-toggle
-      i.icon-th
-      span(data-i18n="play_level.grid") Grid
-      i.icon-ok.secret.invisible
     li.selectable#edit-wizard-settings
       i.icon-user
       span(data-i18n="play_level.customize_wizard") Customize Wizard
diff --git a/app/views/play/level/LevelPlaybackView.coffee b/app/views/play/level/LevelPlaybackView.coffee
index b783d3db4..deddab970 100644
--- a/app/views/play/level/LevelPlaybackView.coffee
+++ b/app/views/play/level/LevelPlaybackView.coffee
@@ -18,8 +18,6 @@ module.exports = class LevelPlaybackView extends CocoView
     'level-scrub-back': 'onScrubBack'
     'level-set-volume': 'onSetVolume'
     'level-set-debug': 'onSetDebug'
-    'level-set-grid': 'onSetGrid'
-    'level-toggle-grid': 'onToggleGrid'
     'surface:frame-changed': 'onFrameChanged'
     'god:new-world-created': 'onNewWorld'
     'god:streaming-world-updated': 'onNewWorld'
@@ -30,7 +28,6 @@ module.exports = class LevelPlaybackView extends CocoView
 
   events:
     'click #debug-toggle': 'onToggleDebug'
-    'click #grid-toggle': 'onToggleGrid'
     'click #edit-wizard-settings': 'onEditWizardSettings'
     'click #edit-editor-config': 'onEditEditorConfig'
     'click #view-keyboard-shortcuts': 'onViewKeyboardShortcuts'
@@ -188,11 +185,6 @@ module.exports = class LevelPlaybackView extends CocoView
     flag = $('#debug-toggle i.icon-ok')
     Backbone.Mediator.publish('level-set-debug', {debug: flag.hasClass('invisible')})
 
-  onToggleGrid: ->
-    return if @shouldIgnore()
-    flag = $('#grid-toggle i.icon-ok')
-    Backbone.Mediator.publish('level-set-grid', {grid: flag.hasClass('invisible')})
-
   onEditWizardSettings: ->
     Backbone.Mediator.publish 'edit-wizard-settings'
 
@@ -316,10 +308,6 @@ module.exports = class LevelPlaybackView extends CocoView
     flag = $('#debug-toggle i.icon-ok')
     flag.toggleClass 'invisible', not e.debug
 
-  onSetGrid: (e) ->
-    flag = $('#grid-toggle i.icon-ok')
-    flag.toggleClass 'invisible', not e.grid
-
   # to refactor
 
   hookUpScrubber: ->