diff --git a/app/lib/surface/Lank.coffee b/app/lib/surface/Lank.coffee
index 4dd1628d2..8f86a3788 100644
--- a/app/lib/surface/Lank.coffee
+++ b/app/lib/surface/Lank.coffee
@@ -474,6 +474,8 @@ module.exports = Lank = class Lank extends CocoClass
       bar.scaleX = healthPct / @options.floatingLayer.resolutionFactor
     if @thang.showsName
       @setNameLabel(if @thang.health <= 0 then '' else @thang.id)
+    else if @options.playerName
+      @setNameLabel @options.playerName
 
   configureMouse: ->
     @sprite.cursor = 'pointer' if @thang?.isSelectable
diff --git a/app/lib/surface/LankBoss.coffee b/app/lib/surface/LankBoss.coffee
index 55a5bbc55..17cab9c9e 100644
--- a/app/lib/surface/LankBoss.coffee
+++ b/app/lib/surface/LankBoss.coffee
@@ -163,6 +163,8 @@ module.exports = class LankBoss extends CocoClass
 
     options = @createLankOptions thang: thang
     options.resolutionFactor = if thangType.get('kind') is 'Floor' then 2 else SPRITE_RESOLUTION_FACTOR
+    if @options.playerNames and /Hero Placeholder/.test thang.id
+      options.playerName = @options.playerNames[thang.team]
     lank = new Lank thangType, options
     @listenTo lank, 'sprite:mouse-up', @onLankMouseUp
     @addLank lank, null, layer
diff --git a/app/lib/surface/Surface.coffee b/app/lib/surface/Surface.coffee
index f001a6330..404075d48 100644
--- a/app/lib/surface/Surface.coffee
+++ b/app/lib/surface/Surface.coffee
@@ -113,7 +113,7 @@ module.exports = Surface = class Surface extends CocoClass
     canvasHeight = parseInt @normalCanvas.attr('height'), 10
     @screenLayer.addChild new Letterbox canvasWidth: canvasWidth, canvasHeight: canvasHeight
 
-    @lankBoss = new LankBoss camera: @camera, webGLStage: @webGLStage, surfaceTextLayer: @surfaceTextLayer, world: @world, thangTypes: @options.thangTypes, choosing: @options.choosing, navigateToSelection: @options.navigateToSelection, showInvisible: @options.showInvisible
+    @lankBoss = new LankBoss camera: @camera, webGLStage: @webGLStage, surfaceTextLayer: @surfaceTextLayer, world: @world, thangTypes: @options.thangTypes, choosing: @options.choosing, navigateToSelection: @options.navigateToSelection, showInvisible: @options.showInvisible, playerNames: @options.playerNames
     @countdownScreen = new CountdownScreen camera: @camera, layer: @screenLayer, showsCountdown: @world.showsCountdown
     @playbackOverScreen = new PlaybackOverScreen camera: @camera, layer: @screenLayer
     @normalStage.addChildAt @playbackOverScreen.dimLayer, 0  # Put this below the other layers, actually, so we can more easily read text on the screen.
@@ -549,8 +549,9 @@ module.exports = Surface = class Surface extends CocoClass
     @normalStage.scaleY *= newHeight / oldHeight
     @camera.onResize newWidth, newHeight
     if @options.spectateGame
-      # Since normalCanvas is absolutely positioned, it needs help aligning with webGLCanvas. But not further than +149px (1920px screen).
-      @normalCanvas.css 'left', Math.min 149, @webGLCanvas.offset().left
+      # Since normalCanvas is absolutely positioned, it needs help aligning with webGLCanvas.
+      offset = @webGLCanvas.offset().left - ($('#page-container').innerWidth() - $('#canvas-wrapper').innerWidth()) / 2
+      @normalCanvas.css 'left', offset
 
   #- Camera focus on hero
   focusOnHero: ->
diff --git a/app/views/play/SpectateView.coffee b/app/views/play/SpectateView.coffee
index 2530e00d5..2d0f66833 100644
--- a/app/views/play/SpectateView.coffee
+++ b/app/views/play/SpectateView.coffee
@@ -187,13 +187,13 @@ module.exports = class SpectateLevelView extends RootView
     ctx.fillText("Loaded #{@modelsLoaded} thingies",50,50)
 
   insertSubviews: ->
-    @insertSubView @tome = new TomeView levelID: @levelID, session: @session, thangs: @world.thangs, supermodel: @supermodel, spectateView: true, spectateOpponentCodeLanguage: @otherSession?.get('submittedCodeLanguage'), level: @level
-    @insertSubView new PlaybackView {}
+    @insertSubView @tome = new TomeView levelID: @levelID, session: @session, otherSession: @otherSession, thangs: @world.thangs, supermodel: @supermodel, spectateView: true, spectateOpponentCodeLanguage: @otherSession?.get('submittedCodeLanguage'), level: @level
+    @insertSubView new PlaybackView session: @session, level: @level
 
     @insertSubView new GoldView {}
-    @insertSubView new HUDView {}
+    @insertSubView new HUDView {level: @level}
     worldName = utils.i18n @level.attributes, 'name'
-    @controlBar = @insertSubView new ControlBarView {worldName: worldName, session: @session, level: @level, supermodel: @supermodel, playableTeams: @world.playableTeams, spectateGame: true}
+    @controlBar = @insertSubView new ControlBarView {worldName: worldName, session: @session, level: @level, supermodel: @supermodel, spectateGame: true}
 
   # callbacks
 
@@ -207,7 +207,7 @@ module.exports = class SpectateLevelView extends RootView
   initSurface: ->
     webGLSurface = $('canvas#webgl-surface', @$el)
     normalSurface = $('canvas#normal-surface', @$el)
-    @surface = new Surface(@world, normalSurface, webGLSurface, thangTypes: @supermodel.getModels(ThangType), playJingle: not @isEditorPreview, spectateGame: true, wizards: @level.get('type', true) isnt 'hero')
+    @surface = new Surface @world, normalSurface, webGLSurface, thangTypes: @supermodel.getModels(ThangType), playJingle: not @isEditorPreview, spectateGame: true, wizards: @level.get('type', true) is 'ladder', playerNames: @findPlayerNames()
     worldBounds = @world.getBounds()
     bounds = [{x:worldBounds.left, y:worldBounds.top}, {x:worldBounds.right, y:worldBounds.bottom}]
     @surface.camera.setBounds(bounds)
@@ -215,6 +215,12 @@ module.exports = class SpectateLevelView extends RootView
       @surface.camera.zoomTo({x: (worldBounds.right - worldBounds.left) / 2, y: (worldBounds.top - worldBounds.bottom) / 2}, 0.1, 0)
     _.delay zoom, 4000  # call it later for some reason (TODO: figure this out)
 
+  findPlayerNames: ->
+    playerNames = {}
+    for session in [@session, @otherSession] when session?.get('team')
+      playerNames[session.get('team')] = session.get('creatorName') or 'Anoner'
+    playerNames
+
   initGoalManager: ->
     @goalManager = new GoalManager(@world, @level.get('goals'))
     @god.setGoalManager @goalManager
diff --git a/app/views/play/level/PlayLevelView.coffee b/app/views/play/level/PlayLevelView.coffee
index 48b302763..26babb926 100644
--- a/app/views/play/level/PlayLevelView.coffee
+++ b/app/views/play/level/PlayLevelView.coffee
@@ -305,12 +305,19 @@ module.exports = class PlayLevelView extends RootView
   initSurface: ->
     webGLSurface = $('canvas#webgl-surface', @$el)
     normalSurface = $('canvas#normal-surface', @$el)
-    @surface = new Surface(@world, normalSurface, webGLSurface, thangTypes: @supermodel.getModels(ThangType), playJingle: not @isEditorPreview, wizards: not (@level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop']))
+    @surface = new Surface(@world, normalSurface, webGLSurface, thangTypes: @supermodel.getModels(ThangType), playJingle: not @isEditorPreview, wizards: not (@level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop']), observing: @observing, playerNames: @findPlayerNames())
     worldBounds = @world.getBounds()
     bounds = [{x: worldBounds.left, y: worldBounds.top}, {x: worldBounds.right, y: worldBounds.bottom}]
     @surface.camera.setBounds(bounds)
     @surface.camera.zoomTo({x: 0, y: 0}, 0.1, 0)
 
+  findPlayerNames: ->
+    return {} unless @observing
+    playerNames = {}
+    for session in [@session, @otherSession] when session?.get('team')
+      playerNames[session.get('team')] = session.get('creatorName') or 'Anoner'
+    playerNames
+
   # Once Surface is Loaded ####################################################
 
   onLevelStarted: ->