diff --git a/app/lib/LevelSetupManager.coffee b/app/lib/LevelSetupManager.coffee
index 0bfd5909c..39492c35b 100644
--- a/app/lib/LevelSetupManager.coffee
+++ b/app/lib/LevelSetupManager.coffee
@@ -76,3 +76,8 @@ module.exports = class LevelSetupManager extends CocoClass
       viewClass: viewClass
       viewArgs: [{supermodel: @supermodel}, @options.levelID]
     }
+
+  destroy: ->
+    @heroesModalDestroy.call @heroesModal unless @heroesModal.destroyed
+    @inventoryModalDestroy.call @inventoryModal unless @inventoryModal.destroyed
+    super()
diff --git a/app/lib/surface/LankBoss.coffee b/app/lib/surface/LankBoss.coffee
index cd68e6b9f..a6703d99e 100644
--- a/app/lib/surface/LankBoss.coffee
+++ b/app/lib/surface/LankBoss.coffee
@@ -230,11 +230,12 @@ module.exports = class LankBoss extends CocoClass
 
   cacheObstacles: (updatedObstacles=null) ->
     return if @cachedObstacles and not updatedObstacles
-    wallLanks = (lank for lank in @lankArray when lank.thangType?.get('name').search(/(dungeon|indoor).wall/i) isnt -1)
+    lankArray = @lankArray
+    wallLanks = (lank for lank in lankArray when lank.thangType?.get('name').search(/(dungeon|indoor).wall/i) isnt -1)
     return if _.any (s.stillLoading for s in wallLanks)
     walls = (lank.thang for lank in wallLanks)
     @world.calculateBounds()
-    wallGrid = new Grid walls, @world.size()...
+    wallGrid = new Grid walls, @world.width, @world.height
     if updatedObstacles
       possiblyUpdatedWallLanks = (lank for lank in wallLanks when _.find updatedObstacles, (w2) -> lank is w2 or (Math.abs(lank.thang.pos.x - w2.thang.pos.x) + Math.abs(lank.thang.pos.y - w2.thang.pos.y)) <= 16)
     else
diff --git a/app/views/game-menu/InventoryModal.coffee b/app/views/game-menu/InventoryModal.coffee
index f8b85a1e3..3d0368cb6 100644
--- a/app/views/game-menu/InventoryModal.coffee
+++ b/app/views/game-menu/InventoryModal.coffee
@@ -434,6 +434,7 @@ module.exports = class InventoryModal extends ModalView
   onHidden: ->
     # Called when the modal itself is dismissed
     @endHighlight()
+    super()
 
   onClickChooseHero: ->
     @hide()
diff --git a/app/views/play/WorldMapView.coffee b/app/views/play/WorldMapView.coffee
index 2b1ddb0a8..3c0d8926f 100644
--- a/app/views/play/WorldMapView.coffee
+++ b/app/views/play/WorldMapView.coffee
@@ -49,6 +49,7 @@ module.exports = class WorldMapView extends RootView
     window.tracker?.trackEvent 'World Map', Action: 'Loaded'
 
   destroy: ->
+    @setupManager?.destroy()
     $(window).off 'resize', @onWindowResize
     if ambientSound = @ambientSound
       # Doesn't seem to work; stops immediately.
@@ -150,8 +151,9 @@ module.exports = class WorldMapView extends RootView
     @startLevel $(e.target).parents('.level-info-container')
 
   startLevel: (levelElement) ->
-    setupManager = new LevelSetupManager supermodel: @supermodel, levelID: levelElement.data('level-id'), levelPath: levelElement.data('level-path'), levelName: levelElement.data('level-name'), hadEverChosenHero: @hadEverChosenHero, parent: @
-    setupManager.open()
+    @setupManager?.destroy()
+    @setupManager = new LevelSetupManager supermodel: @supermodel, levelID: levelElement.data('level-id'), levelPath: levelElement.data('level-path'), levelName: levelElement.data('level-name'), hadEverChosenHero: @hadEverChosenHero, parent: @
+    @setupManager.open()
     @$levelInfo?.hide()
 
   onMouseEnterLevel: (e) ->
diff --git a/app/views/play/level/ControlBarView.coffee b/app/views/play/level/ControlBarView.coffee
index ef2dbbf7d..d759a2806 100644
--- a/app/views/play/level/ControlBarView.coffee
+++ b/app/views/play/level/ControlBarView.coffee
@@ -69,8 +69,9 @@ module.exports = class ControlBarView extends CocoView
     gameMenuModal = new GameMenuModal level: @level, session: @session, supermodel: @supermodel
     @openModalView gameMenuModal
     @listenToOnce gameMenuModal, 'change-hero', ->
-      setupManager = new LevelSetupManager({supermodel: @supermodel, levelID: @level.get('slug'), parent: @})
-      setupManager.open()
+      @setupManager?.destroy()
+      @setupManager = new LevelSetupManager({supermodel: @supermodel, levelID: @level.get('slug'), parent: @})
+      @setupManager.open()
 
   onClickHome: (e) ->
     e.preventDefault()
@@ -84,3 +85,7 @@ module.exports = class ControlBarView extends CocoView
     return if enabled is @controlsEnabled
     @controlsEnabled = enabled
     @$el.toggleClass 'controls-disabled', not enabled
+
+  destroy: ->
+    @setupManager?.destroy()
+    super()
diff --git a/app/views/play/level/PlayLevelView.coffee b/app/views/play/level/PlayLevelView.coffee
index c44ab30fb..b2abfe530 100644
--- a/app/views/play/level/PlayLevelView.coffee
+++ b/app/views/play/level/PlayLevelView.coffee
@@ -280,8 +280,9 @@ module.exports = class PlayLevelView extends RootView
   onSessionLoaded: (e) ->
     # Just the level and session have been loaded by the level loader
     if e.level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop'] and not _.size e.session.get('heroConfig')?.inventory ? {}
-      setupManager = new LevelSetupManager({supermodel: @supermodel, levelID: @levelID, parent: @})
-      setupManager.open()
+      @setupManager?.destroy()
+      @setupManager = new LevelSetupManager({supermodel: @supermodel, levelID: @levelID, parent: @})
+      @setupManager.open()
 
     @onRealTimeMultiplayerLevelLoaded e.session if e.level.get('type') in ['ladder', 'hero-ladder']
 
@@ -556,6 +557,7 @@ module.exports = class PlayLevelView extends RootView
     @god?.destroy()
     @goalManager?.destroy()
     @scriptManager?.destroy()
+    @setupManager?.destroy()
     if ambientSound = @ambientSound
       # Doesn't seem to work; stops immediately.
       createjs.Tween.get(ambientSound).to({volume: 0.0}, 1500).call -> ambientSound.stop()
diff --git a/app/views/play/level/tome/Spell.coffee b/app/views/play/level/tome/Spell.coffee
index 98475a66d..dd494484a 100644
--- a/app/views/play/level/tome/Spell.coffee
+++ b/app/views/play/level/tome/Spell.coffee
@@ -52,13 +52,14 @@ module.exports = class Spell
       @tabView.render()
     @team = @permissions.readwrite[0] ? 'common'
     Backbone.Mediator.publish 'tome:spell-created', spell: @
-    Backbone.Mediator.subscribe 'real-time-multiplayer:new-opponent-code', @onNewOpponentCode
+    Backbone.Mediator.subscribe 'real-time-multiplayer:new-opponent-code', @onNewOpponentCode, @
 
   destroy: ->
     @view?.destroy()
     @tabView?.destroy()
     @thangs = null
     @worker = null
+    Backbone.Mediator.unsubscribe 'real-time-multiplayer:new-opponent-code', @onNewOpponentCode, @
 
   setLanguage: (@language) ->
     #console.log 'setting language to', @language, 'so using original source', @languages[language] ? @languages.javascript
@@ -192,7 +193,7 @@ module.exports = class Spell
     return true if @session.get('creator') isnt me.id and not (me.isAdmin() or 'employer' in me.get('permissions', true))
     false
 
-  onNewOpponentCode: (e) =>
+  onNewOpponentCode: (e) ->
     return unless @spellKey and @canWrite e.team
     if e.codeLanguage and e.code
       [thangSlug, methodSlug] = @spellKey.split '/'