From b9f4093b083d0cdd2114fd8cb99197b2136b19a1 Mon Sep 17 00:00:00 2001
From: Scott Erickson <sderickson@gmail.com>
Date: Mon, 28 Apr 2014 14:58:58 -0700
Subject: [PATCH 01/12] Set up the LevelLoader to load names instead of whole
 ThangTypes.

---
 app/collections/ThangNamesCollection.coffee | 11 +++++++++++
 app/lib/LevelLoader.coffee                  | 18 ++++++++++++------
 app/locale/en.coffee                        |  1 +
 app/models/SuperModel.coffee                |  3 +--
 app/views/play/level_view.coffee            |  5 -----
 server/commons/Handler.coffee               |  8 ++++----
 6 files changed, 29 insertions(+), 17 deletions(-)
 create mode 100644 app/collections/ThangNamesCollection.coffee

diff --git a/app/collections/ThangNamesCollection.coffee b/app/collections/ThangNamesCollection.coffee
new file mode 100644
index 000000000..1897d8b12
--- /dev/null
+++ b/app/collections/ThangNamesCollection.coffee
@@ -0,0 +1,11 @@
+ThangType = require 'models/ThangType'
+CocoCollection = require 'collections/CocoCollection'
+
+module.exports = class ThangNamesCollection extends CocoCollection
+  url: '/db/thang.type/names'
+  model: ThangType
+  isCachable: false
+
+  constructor: (ids) ->
+    console.log 'data', {type:'POST', data:{ids:ids}}
+    super([], {type:'POST', data:{ids:ids}})
diff --git a/app/lib/LevelLoader.coffee b/app/lib/LevelLoader.coffee
index 5063982d9..4404acb17 100644
--- a/app/lib/LevelLoader.coffee
+++ b/app/lib/LevelLoader.coffee
@@ -4,6 +4,7 @@ LevelSystem = require 'models/LevelSystem'
 Article = require 'models/Article'
 LevelSession = require 'models/LevelSession'
 ThangType = require 'models/ThangType'
+ThangNamesCollection = require 'collections/ThangNamesCollection'
 
 CocoClass = require 'lib/CocoClass'
 AudioPlayer = require 'lib/AudioPlayer'
@@ -21,6 +22,7 @@ World = require 'lib/world/world'
 module.exports = class LevelLoader extends CocoClass
 
   constructor: (options) ->
+    @t0 = new Date().getTime()
     super()
     @supermodel = options.supermodel
     @levelID = options.levelID
@@ -98,12 +100,15 @@ module.exports = class LevelLoader extends CocoClass
       articleVersions.push _.pick(article, ['original', 'majorVersion'])
 
     objUniq = (array) -> _.uniq array, false, (arg) -> JSON.stringify(arg)
-      
-    for thangID in _.uniq thangIDs
-      url = "/db/thang.type/#{thangID}/version"
-      url += "?project=true" if @headless and not @editorMode
-      res = @maybeLoadURL url, ThangType, 'thang'
-      @listenToOnce res.model, 'sync', @buildSpriteSheetsForThangType if res
+
+    thangNames = new ThangNamesCollection(thangIDs)
+    @supermodel.loadCollection thangNames, 'thang_names'
+#    for thangID in _.uniq thangIDs
+#      url = "/db/thang.type/#{thangID}/version"
+#      url += "?project=true" if @headless and not @editorMode
+#      res = @maybeLoadURL url, ThangType, 'thang'
+#      @listenToOnce res.model, 'sync', @buildSpriteSheetsForThangType if res
+    
     for obj in objUniq componentVersions
       url = "/db/level.component/#{obj.original}/version/#{obj.majorVersion}"
       @maybeLoadURL url, LevelComponent, 'component'
@@ -192,6 +197,7 @@ module.exports = class LevelLoader extends CocoClass
     @world = new World @level.get('name')
     serializedLevel = @level.serialize(@supermodel)
     @world.loadFromLevel serializedLevel, false
+    console.log 'INITIALIZED!', (new Date().getTime() - @t0) / 1000
 
   # Initial Sound Loading
 
diff --git a/app/locale/en.coffee b/app/locale/en.coffee
index 0f2194e8d..3a4b332a7 100644
--- a/app/locale/en.coffee
+++ b/app/locale/en.coffee
@@ -695,5 +695,6 @@
     opponent_session: "Opponent Session"
     article: "Article"
     user_names: "User Names"
+    thang_names: "Thang Names"
     files: "Files"
     top_simulators: "Top Simulators"
diff --git a/app/models/SuperModel.coffee b/app/models/SuperModel.coffee
index 4a296d4a6..0ca6fd7fd 100644
--- a/app/models/SuperModel.coffee
+++ b/app/models/SuperModel.coffee
@@ -80,7 +80,6 @@ module.exports = class SuperModel extends Backbone.Model
     return _.values @models
 
   registerModel: (model) ->
-    console.log 'register model?', model, model.getURL
     @models[model.getURL()] = model
 
   getCollection: (collection) ->
@@ -94,7 +93,7 @@ module.exports = class SuperModel extends Backbone.Model
     @registerCollection(collection)
 
   registerCollection: (collection) ->
-    @collections[collection.getURL()] = collection
+    @collections[collection.getURL()] = collection if collection.isCachable
     # consolidate models
     for model, i in collection.models
       cachedModel = @getModelByURL(model.getURL())
diff --git a/app/views/play/level_view.coffee b/app/views/play/level_view.coffee
index 0118b5242..3d976e032 100644
--- a/app/views/play/level_view.coffee
+++ b/app/views/play/level_view.coffee
@@ -83,7 +83,6 @@ module.exports = class PlayLevelView extends View
     @sessionID = @getQueryVariable 'session'
 
     $(window).on('resize', @onWindowResize)
-    @listenToOnce(@supermodel, 'error', @onLevelLoadError)
     @saveScreenshot = _.throttle @saveScreenshot, 30000
 
     if @isEditorPreview
@@ -96,10 +95,6 @@ module.exports = class PlayLevelView extends View
       @load()
       application.tracker?.trackEvent 'Started Level Load', level: @levelID, label: @levelID
 
-  onLevelLoadError: (e) ->
-    # TODO NOW: remove this in favor of the supermodel handling it
-    application.router.navigate "/play?not_found=#{@levelID}", {trigger: true}
-
   setLevel: (@level, @supermodel) ->
     @god?.level = @level.serialize @supermodel
     if @world
diff --git a/server/commons/Handler.coffee b/server/commons/Handler.coffee
index b4590b2f0..e3b7911d8 100644
--- a/server/commons/Handler.coffee
+++ b/server/commons/Handler.coffee
@@ -112,7 +112,7 @@ module.exports = class Handler
     ids = ids.split(',') if _.isString ids
     ids = _.uniq ids
 
-    project = {name:1}
+    project = {name:1, original:1}
     sort = {'version.major':-1, 'version.minor':-1}
 
     makeFunc = (id) =>
@@ -120,8 +120,8 @@ module.exports = class Handler
         criteria = {original:mongoose.Types.ObjectId(id)}
         @modelClass.findOne(criteria, project).sort(sort).exec (err, document) ->
           return done(err) if err
-          callback(null, document?.toObject() or {})
-
+          callback(null, document?.toObject() or null)
+          
     funcs = {}
     for id in ids
       return errors.badInput(res, "Given an invalid id: #{id}") unless Handler.isID(id)
@@ -129,7 +129,7 @@ module.exports = class Handler
 
     async.parallel funcs, (err, results) ->
       return errors.serverError err if err
-      res.send results
+      res.send (d for d in _.values(results) when d)
       res.end()
 
   getPatchesFor: (req, res, id) ->

From 2952d7702c202ed7ba61fd678b2622a476213645 Mon Sep 17 00:00:00 2001
From: Scott Erickson <sderickson@gmail.com>
Date: Fri, 2 May 2014 08:20:03 -0700
Subject: [PATCH 02/12] Merge branch 'master' into feature/thangload

Conflicts:
	app/lib/LevelLoader.coffee
	app/views/play/level_view.coffee
---
 app/lib/LevelLoader.coffee       | 9 ---------
 app/views/play/level_view.coffee | 5 -----
 2 files changed, 14 deletions(-)

diff --git a/app/lib/LevelLoader.coffee b/app/lib/LevelLoader.coffee
index edd8f8881..26c743925 100644
--- a/app/lib/LevelLoader.coffee
+++ b/app/lib/LevelLoader.coffee
@@ -105,7 +105,6 @@ module.exports = class LevelLoader extends CocoClass
 
     objUniq = (array) -> _.uniq array, false, (arg) -> JSON.stringify(arg)
 
-<<<<<<< HEAD
     thangNames = new ThangNamesCollection(thangIDs)
     @supermodel.loadCollection thangNames, 'thang_names'
 #    for thangID in _.uniq thangIDs
@@ -113,14 +112,6 @@ module.exports = class LevelLoader extends CocoClass
 #      url += "?project=true" if @headless and not @editorMode
 #      res = @maybeLoadURL url, ThangType, 'thang'
 #      @listenToOnce res.model, 'sync', @buildSpriteSheetsForThangType if res
-    
-=======
-    for thangID in _.uniq thangIDs
-      url = "/db/thang.type/#{thangID}/version"
-      url += "?project=true" if @headless and not @editorMode
-      res = @maybeLoadURL url, ThangType, 'thang'
-      @listenToOnce res.model, 'sync', @buildSpriteSheetsForThangType if res
->>>>>>> master
     for obj in objUniq componentVersions
       url = "/db/level.component/#{obj.original}/version/#{obj.majorVersion}"
       @maybeLoadURL url, LevelComponent, 'component'
diff --git a/app/views/play/level_view.coffee b/app/views/play/level_view.coffee
index cae22214c..5fd97af78 100644
--- a/app/views/play/level_view.coffee
+++ b/app/views/play/level_view.coffee
@@ -93,9 +93,6 @@ module.exports = class PlayLevelView extends View
       @load()
       application.tracker?.trackEvent 'Started Level Load', level: @levelID, label: @levelID
 
-<<<<<<< HEAD
-  setLevel: (@level, @supermodel) ->
-=======
   onLevelLoadError: (e) ->
     # TODO NOW: remove this in favor of the supermodel handling it
     application.router.navigate "/play?not_found=#{@levelID}", {trigger: true}
@@ -104,8 +101,6 @@ module.exports = class PlayLevelView extends View
     @supermodel.models = givenSupermodel.models
     @supermodel.collections = givenSupermodel.collections
     @supermodel.shouldSaveBackups = givenSupermodel.shouldSaveBackups
-
->>>>>>> master
     @god?.level = @level.serialize @supermodel
     if @world
       serializedLevel = @level.serialize(@supermodel)

From 06bc96d7d045fa84fe3164a252e7953f7cf5a835 Mon Sep 17 00:00:00 2001
From: Scott Erickson <sderickson@gmail.com>
Date: Fri, 2 May 2014 10:31:20 -0700
Subject: [PATCH 03/12] Set up the level loader to get thangtype names first,
 then load the thang types themselves so the world can be generated in
 parallel with thang type loading and rendering.

---
 app/collections/CocoCollection.coffee       |  7 ++++-
 app/collections/ThangNamesCollection.coffee |  9 ++++---
 app/lib/LevelLoader.coffee                  | 30 +++++++++++++--------
 app/lib/surface/SpriteBoss.coffee           |  4 +--
 app/models/CocoModel.coffee                 |  6 +++--
 app/models/SuperModel.coffee                |  1 +
 6 files changed, 38 insertions(+), 19 deletions(-)

diff --git a/app/collections/CocoCollection.coffee b/app/collections/CocoCollection.coffee
index 64074a327..817b0700f 100644
--- a/app/collections/CocoCollection.coffee
+++ b/app/collections/CocoCollection.coffee
@@ -8,4 +8,9 @@ module.exports = class CocoCollection extends Backbone.Collection
       model.loaded = true for model in @models
 
   getURL: ->
-    return if _.isString @url then @url else @url()
\ No newline at end of file
+    return if _.isString @url then @url else @url()
+
+  fetch: ->
+    @jqxhr = super(arguments...)
+    @loading = true
+    @jqxhr
diff --git a/app/collections/ThangNamesCollection.coffee b/app/collections/ThangNamesCollection.coffee
index 1897d8b12..414628d84 100644
--- a/app/collections/ThangNamesCollection.coffee
+++ b/app/collections/ThangNamesCollection.coffee
@@ -6,6 +6,9 @@ module.exports = class ThangNamesCollection extends CocoCollection
   model: ThangType
   isCachable: false
 
-  constructor: (ids) ->
-    console.log 'data', {type:'POST', data:{ids:ids}}
-    super([], {type:'POST', data:{ids:ids}})
+  constructor: (@ids) -> super()
+    
+  fetch: (options) ->
+    options ?= {}
+    _.extend options, {type:'POST', data:{ids:@ids}}
+    super(options)
diff --git a/app/lib/LevelLoader.coffee b/app/lib/LevelLoader.coffee
index 26c743925..074d8c76d 100644
--- a/app/lib/LevelLoader.coffee
+++ b/app/lib/LevelLoader.coffee
@@ -105,19 +105,18 @@ module.exports = class LevelLoader extends CocoClass
 
     objUniq = (array) -> _.uniq array, false, (arg) -> JSON.stringify(arg)
 
-    thangNames = new ThangNamesCollection(thangIDs)
-    @supermodel.loadCollection thangNames, 'thang_names'
-#    for thangID in _.uniq thangIDs
-#      url = "/db/thang.type/#{thangID}/version"
-#      url += "?project=true" if @headless and not @editorMode
-#      res = @maybeLoadURL url, ThangType, 'thang'
-#      @listenToOnce res.model, 'sync', @buildSpriteSheetsForThangType if res
+    worldNecessities = []
+
+    @thangIDs = _.uniq thangIDs
+    thangNames = new ThangNamesCollection(@thangIDs)
+    worldNecessities.push @supermodel.loadCollection(thangNames, 'thang_names')
+          
     for obj in objUniq componentVersions
       url = "/db/level.component/#{obj.original}/version/#{obj.majorVersion}"
-      @maybeLoadURL url, LevelComponent, 'component'
+      worldNecessities.push @maybeLoadURL(url, LevelComponent, 'component')
     for obj in objUniq systemVersions
       url = "/db/level.system/#{obj.original}/version/#{obj.majorVersion}"
-      @maybeLoadURL url, LevelSystem, 'system'
+      worldNecessities.push @maybeLoadURL(url, LevelSystem, 'system')
     for obj in objUniq articleVersions
       url = "/db/article/#{obj.original}/version/#{obj.majorVersion}"
       @maybeLoadURL url, Article, 'article'
@@ -128,6 +127,17 @@ module.exports = class LevelLoader extends CocoClass
     unless @headless and not @editorMode
       wizard = ThangType.loadUniversalWizard()
       @supermodel.loadModel wizard, 'thang'
+      
+    jqxhrs = (resource.jqxhr for resource in worldNecessities when resource?.jqxhr)
+    $.when(jqxhrs...).done(@onWorldNecessitiesLoaded)
+
+  onWorldNecessitiesLoaded: =>
+    @initWorld()
+    for thangID in @thangIDs
+      url = "/db/thang.type/#{thangID}/version"
+      url += "?project=true" if @headless and not @editorMode
+      res = @maybeLoadURL url, ThangType, 'thang'
+      @listenToOnce res.model, 'sync', @buildSpriteSheetsForThangType if res
 
   maybeLoadURL: (url, Model, resourceName) ->
     return if @supermodel.getModel(url)
@@ -138,7 +148,6 @@ module.exports = class LevelLoader extends CocoClass
     @loadLevelSounds()
     @denormalizeSession()
     app.tracker.updatePlayState(@level, @session) unless @headless
-    @initWorld()
 
   denormalizeSession: ->
     return if @headless or @sessionDenormalized or @spectateMode
@@ -201,7 +210,6 @@ module.exports = class LevelLoader extends CocoClass
     @world = new World @level.get('name')
     serializedLevel = @level.serialize(@supermodel)
     @world.loadFromLevel serializedLevel, false
-    console.log 'INITIALIZED!', (new Date().getTime() - @t0) / 1000
 
   # Initial Sound Loading
 
diff --git a/app/lib/surface/SpriteBoss.coffee b/app/lib/surface/SpriteBoss.coffee
index 03e05e0c3..1eb7aa8b7 100644
--- a/app/lib/surface/SpriteBoss.coffee
+++ b/app/lib/surface/SpriteBoss.coffee
@@ -48,7 +48,7 @@ module.exports = class SpriteBoss extends CocoClass
   toString: -> "<SpriteBoss: #{@spriteArray.length} sprites>"
 
   thangTypeFor: (type) ->
-    _.find @options.thangTypes, (m) -> m.get('original') is type or m.get('name') is type
+    _.find @options.thangTypes, (m) -> m.get('actions') and m.get('original') is type or m.get('name') is type
 
   createLayers: ->
     @spriteLayers = {}
@@ -145,7 +145,7 @@ module.exports = class SpriteBoss extends CocoClass
 
   addThangToSprites: (thang, layer=null) ->
     return console.warn 'Tried to add Thang to the surface it already has:', thang.id if @sprites[thang.id]
-    thangType = _.find @options.thangTypes, (m) -> m.get('name') is thang.spriteName
+    thangType = _.find @options.thangTypes, (m) -> m.get('actions') and m.get('name') is thang.spriteName
     options = @createSpriteOptions thang: thang
     options.resolutionFactor = if thangType.get('kind') is 'Floor' then 2 else 4
     sprite = new CocoSprite thangType, options
diff --git a/app/models/CocoModel.coffee b/app/models/CocoModel.coffee
index a7d408adf..ab2737ad4 100644
--- a/app/models/CocoModel.coffee
+++ b/app/models/CocoModel.coffee
@@ -36,6 +36,8 @@ class CocoModel extends Backbone.Model
     @loading = false
     @markToRevert()
     @loadFromBackup()
+    
+  getNormalizedURL: -> "#{@urlRoot}/#{@id}"
 
   set: ->
     res = super(arguments...)
@@ -75,9 +77,9 @@ class CocoModel extends Backbone.Model
     return super attrs, options
 
   fetch: ->
-    res = super(arguments...)
+    @jqxhr = super(arguments...)
     @loading = true
-    res
+    @jqxhr
 
   markToRevert: ->
     if @type() is 'ThangType'
diff --git a/app/models/SuperModel.coffee b/app/models/SuperModel.coffee
index 14f08ef11..f2bc57de4 100644
--- a/app/models/SuperModel.coffee
+++ b/app/models/SuperModel.coffee
@@ -199,6 +199,7 @@ class ModelResource extends Resource
     super(name, value)
     @model = modelOrCollection
     @fetchOptions = fetchOptions
+    @jqxhr = @model.jqxhr
 
   load: ->
     @markLoading()

From d54149d0f31e302ac5ec94c3bc1cbc0495d0fa09 Mon Sep 17 00:00:00 2001
From: Nick Winter <livelily@gmail.com>
Date: Fri, 2 May 2014 12:32:41 -0700
Subject: [PATCH 04/12] Reworked LevelView loading priorities to try to get
 world simulating earlier.

---
 .../javascripts/workers/aether_worker.js      |  4 +-
 app/lib/God.coffee                            |  7 ++--
 app/lib/LevelLoader.coffee                    |  5 ++-
 app/locale/en.coffee                          |  1 +
 app/styles/editor/patches.sass                |  3 ++
 app/views/editor/patch_modal.coffee           | 18 ++++-----
 app/views/editor/patches_view.coffee          | 14 +++----
 app/views/play/level/tome/spell_view.coffee   |  2 +-
 app/views/play/level_view.coffee              | 37 +++++++++++--------
 9 files changed, 50 insertions(+), 41 deletions(-)

diff --git a/app/assets/javascripts/workers/aether_worker.js b/app/assets/javascripts/workers/aether_worker.js
index 9fbad8155..4bda39f6a 100644
--- a/app/assets/javascripts/workers/aether_worker.js
+++ b/app/assets/javascripts/workers/aether_worker.js
@@ -2,7 +2,7 @@ var window = self;
 var Global = self;
 
 importScripts("/javascripts/tome_aether.js");
-console.log("imported scripts!");
+console.log("Aether Tome worker has finished importing scripts.");
 var aethers = {};
 
 var createAether = function (spellKey, options) 
@@ -96,4 +96,4 @@ self.addEventListener('message', function(e) {
         var returnObject = {"message":message, "function":"none"};
         self.postMessage(JSON.stringify(returnObject));
     }
-}, false); 
\ No newline at end of file
+}, false); 
diff --git a/app/lib/God.coffee b/app/lib/God.coffee
index 6fe758391..7a71d717f 100644
--- a/app/lib/God.coffee
+++ b/app/lib/God.coffee
@@ -17,11 +17,11 @@ module.exports = class God
     @id = God.nextID()
     options ?= {}
     @maxAngels = options.maxAngels ? 2  # How many concurrent web workers to use; if set past 8, make up more names
-    @maxWorkerPoolSize = options.maxWorkerPoolSize ? 2  # ~20MB per idle worker
+    @maxWorkerPoolSize = options.maxWorkerPoolSize ? 1  # ~20MB per idle worker
     @angels = []
     @firstWorld = true
     Backbone.Mediator.subscribe 'tome:cast-spells', @onTomeCast, @
-    @fillWorkerPool = _.throttle @fillWorkerPool, 3000, leading: false
+    @fillWorkerPool = _.throttle @fillWorkerPool, 3000
     @fillWorkerPool()
 
   onTomeCast: (e) ->
@@ -87,7 +87,7 @@ module.exports = class God
     Backbone.Mediator.publish 'god:user-code-problem', problem: problem
 
   createWorld: ->
-    #console.log @id + ': "Let there be light upon', @world.name + '!"'
+    console.log @id + ': "Let there be light upon', @level.name + '!"'
     unless Worker?  # profiling world simulation is easier on main thread, or we are IE9
       setTimeout @simulateWorld, 1
       return
@@ -98,7 +98,6 @@ module.exports = class God
     else
       @worldWaiting = true
       return
-    #console.log "going to run world with code", @getUserCodeMap()
     angel.worker.postMessage {func: 'runWorld', args: {
       worldName: @level.name
       userCodeMap: @getUserCodeMap()
diff --git a/app/lib/LevelLoader.coffee b/app/lib/LevelLoader.coffee
index 074d8c76d..c709d6814 100644
--- a/app/lib/LevelLoader.coffee
+++ b/app/lib/LevelLoader.coffee
@@ -110,7 +110,7 @@ module.exports = class LevelLoader extends CocoClass
     @thangIDs = _.uniq thangIDs
     thangNames = new ThangNamesCollection(@thangIDs)
     worldNecessities.push @supermodel.loadCollection(thangNames, 'thang_names')
-          
+
     for obj in objUniq componentVersions
       url = "/db/level.component/#{obj.original}/version/#{obj.majorVersion}"
       worldNecessities.push @maybeLoadURL(url, LevelComponent, 'component')
@@ -127,7 +127,7 @@ module.exports = class LevelLoader extends CocoClass
     unless @headless and not @editorMode
       wizard = ThangType.loadUniversalWizard()
       @supermodel.loadModel wizard, 'thang'
-      
+
     jqxhrs = (resource.jqxhr for resource in worldNecessities when resource?.jqxhr)
     $.when(jqxhrs...).done(@onWorldNecessitiesLoaded)
 
@@ -210,6 +210,7 @@ module.exports = class LevelLoader extends CocoClass
     @world = new World @level.get('name')
     serializedLevel = @level.serialize(@supermodel)
     @world.loadFromLevel serializedLevel, false
+    console.log "World has been initialized from level loader."
 
   # Initial Sound Loading
 
diff --git a/app/locale/en.coffee b/app/locale/en.coffee
index 3a4b332a7..9b3469b9c 100644
--- a/app/locale/en.coffee
+++ b/app/locale/en.coffee
@@ -685,6 +685,7 @@
     user_schema: "User Schema"
     user_profile: "User Profile"
     patches: "Patches"
+    patched_model: "Source Document"
     model: "Model"
     system: "System"
     component: "Component"
diff --git a/app/styles/editor/patches.sass b/app/styles/editor/patches.sass
index 110370137..f4130ec5a 100644
--- a/app/styles/editor/patches.sass
+++ b/app/styles/editor/patches.sass
@@ -1,3 +1,6 @@
 .patches-view
   .status-buttons
     margin-bottom: 10px
+
+  .patch-icon
+    cursor: pointer
diff --git a/app/views/editor/patch_modal.coffee b/app/views/editor/patch_modal.coffee
index f5dc339cf..6436ce943 100644
--- a/app/views/editor/patch_modal.coffee
+++ b/app/views/editor/patch_modal.coffee
@@ -8,7 +8,7 @@ module.exports = class PatchModal extends ModalView
   template: template
   plain: true
   modalWidthPercent: 60
-  
+
   events:
     'click #withdraw-button': 'withdrawPatch'
     'click #reject-button': 'rejectPatch'
@@ -24,8 +24,8 @@ module.exports = class PatchModal extends ModalView
       @originalSource = new targetModel.constructor({_id:targetID})
       @originalSource.fetch()
       @listenToOnce @originalSource, 'sync', @onOriginalLoaded
-      @addResourceToLoad(@originalSource)
-      
+      @supermodel.addModelResource @originalSource, 'patched_model'  # TODO: Doesn't work?
+
   getRenderData: ->
     c = super()
     c.isPatchCreator = @patch.get('creator') is auth.me.id
@@ -33,14 +33,14 @@ module.exports = class PatchModal extends ModalView
     c.status = @patch.get 'status'
     c.patch = @patch
     c
-    
+
   afterRender: ->
     return if @originalSource.loading
     headModel = null
     if @targetModel.hasWriteAccess()
       headModel = @originalSource.clone(false)
       headModel.set(@targetModel.attributes)
-    
+
     pendingModel = @originalSource.clone(false)
     pendingModel.applyDelta(@patch.get('delta'))
 
@@ -48,18 +48,18 @@ module.exports = class PatchModal extends ModalView
     changeEl = @$el.find('.changes-stub')
     @insertSubView(@deltaView, changeEl)
     super()
-    
+
   acceptPatch: ->
     delta = @deltaView.getApplicableDelta()
     @targetModel.applyDelta(delta)
     @patch.setStatus('accepted')
     @trigger 'accepted-patch'
     @hide()
-    
+
   rejectPatch: ->
     @patch.setStatus('rejected')
     @hide()
-    
+
   withdrawPatch: ->
     @patch.setStatus('withdrawn')
-    @hide()
\ No newline at end of file
+    @hide()
diff --git a/app/views/editor/patches_view.coffee b/app/views/editor/patches_view.coffee
index 1c005df16..17aef667b 100644
--- a/app/views/editor/patches_view.coffee
+++ b/app/views/editor/patches_view.coffee
@@ -8,7 +8,7 @@ module.exports = class PatchesView extends CocoView
   template: template
   className: 'patches-view'
   status: 'pending'
-  
+
   events:
     'change .status-buttons': 'onStatusButtonsChanged'
     'click .patch-icon': 'openPatchModal'
@@ -16,17 +16,16 @@ module.exports = class PatchesView extends CocoView
   constructor: (@model, options) ->
     super(options)
     @initPatches()
-    
+
   initPatches: ->
     @startedLoading = false
     @patches = new PatchesCollection([], {}, @model, @status)
-    
+
   load: ->
-    console.log 'load patches view?'
     @initPatches()
     @patches = @supermodel.loadCollection(@patches, 'patches').model
     @listenTo @patches, 'sync', @onPatchesLoaded
-    
+
   onPatchesLoaded: ->
     ids = (p.get('creator') for p in @patches.models)
     jqxhrOptions = nameLoader.loadNames ids
@@ -38,19 +37,20 @@ module.exports = class PatchesView extends CocoView
     c.patches = @patches.models
     c.status
     c
-  
+
   afterRender: ->
     @$el.find(".#{@status}").addClass 'active'
 
   onStatusButtonsChanged: (e) ->
     @status = $(e.target).val()
     @reloadPatches()
-    
+
   reloadPatches: ->
     @load()
     @render()
 
   openPatchModal: (e) ->
+    console.log "open patch modal"
     patch = _.find @patches.models, {id:$(e.target).data('patch-id')}
     modal = new PatchModal(patch, @model)
     @openModalView(modal)
diff --git a/app/views/play/level/tome/spell_view.coffee b/app/views/play/level/tome/spell_view.coffee
index 1e2f5ed3a..ace34a266 100644
--- a/app/views/play/level/tome/spell_view.coffee
+++ b/app/views/play/level/tome/spell_view.coffee
@@ -64,7 +64,7 @@ module.exports = class SpellView extends View
       @createFirepad()
     else
       # needs to happen after the code generating this view is complete
-      setTimeout @onAllLoaded, 1
+      _.defer @onAllLoaded
 
   createACE: ->
     # Test themes and settings here: http://ace.ajax.org/build/kitchen-sink.html
diff --git a/app/views/play/level_view.coffee b/app/views/play/level_view.coffee
index 5fd97af78..56e219ec3 100644
--- a/app/views/play/level_view.coffee
+++ b/app/views/play/level_view.coffee
@@ -110,8 +110,8 @@ module.exports = class PlayLevelView extends View
 
   load: ->
     @loadStartTime = new Date()
-    @levelLoader = new LevelLoader supermodel: @supermodel, levelID: @levelID, sessionID: @sessionID, opponentSessionID: @getQueryVariable('opponent'), team: @getQueryVariable("team")
     @god = new God()
+    @levelLoader = new LevelLoader supermodel: @supermodel, levelID: @levelID, sessionID: @sessionID, opponentSessionID: @getQueryVariable('opponent'), team: @getQueryVariable("team")
 
   getRenderData: ->
     c = super()
@@ -131,6 +131,9 @@ module.exports = class PlayLevelView extends View
 
   updateProgress: (progress) ->
     super(progress)
+    if not @worldInitialized and @levelLoader.session.loaded and @levelLoader.level.loaded and @levelLoader.world and (not @levelLoader.opponentSession or @levelLoader.opponentSession.loaded)
+      @grabLevelLoaderData()
+      @onWorldInitialized()
     return if @seenDocs
     return unless @levelLoader.session.loaded and @levelLoader.level.loaded
     return unless showFrequency = @levelLoader.level.get('showsGuide')
@@ -142,6 +145,21 @@ module.exports = class PlayLevelView extends View
       return unless article.loaded
     @showGuide()
 
+  onWorldInitialized: ->
+    @worldInitialized = true
+    team = @getQueryVariable("team") ? @world.teamForPlayer(0)
+    @loadOpponentTeam(team)
+    @god.level = @level.serialize @supermodel
+    @god.worldClassMap = @world.classMap
+    @setTeam team
+    @initGoalManager()
+    @insertSubviews ladderGame: (@level.get('type') is "ladder")
+    @initVolume()
+    @listenTo(@session, 'change:multiplayer', @onMultiplayerChanged)
+    @originalSessionState = $.extend(true, {}, @session.get('state'))
+    @register()
+    @controlBar.setBus(@bus)
+
   showGuide: ->
     @seenDocs = true
     DocsModal = require './level/modal/docs_modal'
@@ -163,21 +181,10 @@ module.exports = class PlayLevelView extends View
     if not (@levelLoader.level.get('type') in ['ladder', 'ladder-tutorial'])
       me.set('lastLevel', @levelID)
       me.save()
-    @grabLevelLoaderData()
-    team = @getQueryVariable("team") ? @world.teamForPlayer(0)
-    @loadOpponentTeam(team)
-    @god.level = @level.serialize @supermodel
-    @god.worldClassMap = @world.classMap
-    @setTeam team
+    @levelLoader.destroy()
+    @levelLoader = null
     @initSurface()
-    @initGoalManager()
     @initScriptManager()
-    @insertSubviews ladderGame: (@level.get('type') is "ladder")
-    @initVolume()
-    @listenTo(@session, 'change:multiplayer', @onMultiplayerChanged)
-    @originalSessionState = $.extend(true, {}, @session.get('state'))
-    @register()
-    @controlBar.setBus(@bus)
     @surface.showLevel()
     if @otherSession
       # TODO: colorize name and cloud by team, colorize wizard by user's color config
@@ -188,8 +195,6 @@ module.exports = class PlayLevelView extends View
     @world = @levelLoader.world
     @level = @levelLoader.level
     @otherSession = @levelLoader.opponentSession
-    @levelLoader.destroy()
-    @levelLoader = null
 
   loadOpponentTeam: (myTeam) ->
     opponentSpells = []

From aee90c72bb9e152c2b3a09b7694d87eeaf7e3d72 Mon Sep 17 00:00:00 2001
From: Scott Erickson <sderickson@gmail.com>
Date: Fri, 2 May 2014 17:03:30 -0700
Subject: [PATCH 05/12] Most of the way there with the raster images. Just need
 to figure out loading with PreloadJS and the SuperModel.

---
 app/lib/LevelLoader.coffee                    |  4 ++
 app/lib/surface/CocoSprite.coffee             | 50 +++++++++++++++----
 app/lib/surface/SpriteBoss.coffee             | 10 ++--
 app/models/ThangType.coffee                   |  4 +-
 app/schemas/models/thang_type.coffee          |  1 +
 app/views/editor/thang/edit.coffee            | 28 ++++++++++-
 app/views/play/level/hud_view.coffee          | 15 +++---
 .../levels/thangs/thang_type_handler.coffee   | 29 +++++------
 8 files changed, 104 insertions(+), 37 deletions(-)

diff --git a/app/lib/LevelLoader.coffee b/app/lib/LevelLoader.coffee
index 074d8c76d..e7ee8d423 100644
--- a/app/lib/LevelLoader.coffee
+++ b/app/lib/LevelLoader.coffee
@@ -168,6 +168,10 @@ module.exports = class LevelLoader extends CocoClass
   # Building sprite sheets
 
   buildSpriteSheetsForThangType: (thangType) ->
+    # TODO: Finish making sure the supermodel loads the raster image before triggering load complete, and that the cocosprite has access to the asset.
+#    if f = thangType.get('raster')
+#      queue = new createjs.LoadQueue()
+#      queue.loadFile('/file/'+f)
     @grabThangTypeTeams() unless @thangTypeTeams
     for team in @thangTypeTeams[thangType.get('original')] ? [null]
       spriteOptions = {resolutionFactor: 4, async: false}
diff --git a/app/lib/surface/CocoSprite.coffee b/app/lib/surface/CocoSprite.coffee
index 55a1982e4..8c5a4de7d 100644
--- a/app/lib/surface/CocoSprite.coffee
+++ b/app/lib/surface/CocoSprite.coffee
@@ -70,7 +70,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
     @age = 0
     @scaleFactor = @targetScaleFactor = 1
     @displayObject = new createjs.Container()
-    if @thangType.get('actions')
+    if @thangType.get('actions') or @thangType.get('raster')
       @setupSprite()
     else
       @stillLoading = true
@@ -79,9 +79,29 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
 
   setupSprite: ->
     @stillLoading = false
-    @actions = @thangType.getActions()
-    @buildFromSpriteSheet @buildSpriteSheet()
-    @createMarks()
+    if @thangType.get('raster')
+      @isRaster = true
+      @setUpRasterImage()
+      @actions = {}
+    else
+      @actions = @thangType.getActions()
+      @buildFromSpriteSheet @buildSpriteSheet()
+      @createMarks()
+
+  setUpRasterImage: ->
+    raster = @thangType.get('raster')
+    sprite = @imageObject = new createjs.Bitmap('/file/'+raster)
+    @displayObject.addChild(sprite)
+    @configureMouse()
+    @originalScaleX = sprite.scaleX
+    @originalScaleY = sprite.scaleY
+    @displayObject.sprite = @
+    @displayObject.layerPriority = @thangType.get 'layerPriority'
+    @displayObject.name = @thang?.spriteName or @thangType.get 'name'
+    reg = @getOffset 'registration'
+    @imageObject.regX = -reg.x
+    @imageObject.regY = -reg.y
+    @updateScale()
 
   destroy: ->
     mark.destroy() for name, mark of @marks
@@ -143,6 +163,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
     @playAction(@actionQueue.splice(0,1)[0]) if @actionQueue.length
 
   playAction: (action) ->
+    return if @isRaster
     @currentAction = action
     return @hide() unless action.animation or action.container or action.relatedActions
     @show()
@@ -244,13 +265,14 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
   updateScale: ->
     if @thangType.get('matchWorldDimensions') and @thang
       if @thang.width isnt @lastThangWidth or @thang.height isnt @lastThangHeight
-        [@lastThangWidth, @lastThangHeight] = [@thang.width, @thang.height]
         bounds = @imageObject.getBounds()
+        return unless bounds # TODO: remove this because it's a bandaid over the image sometimes not being loaded
         @imageObject.scaleX = @thang.width * Camera.PPM / bounds.width
         @imageObject.scaleY = @thang.height * Camera.PPM * @options.camera.y2x / bounds.height
         unless @thang.spriteName is 'Beam'
           @imageObject.scaleX *= @thangType.get('scale') ? 1
           @imageObject.scaleY *= @thangType.get('scale') ? 1
+        [@lastThangWidth, @lastThangHeight] = [@thang.width, @thang.height]
       return
     scaleX = if @getActionProp 'flipX' then -1 else 1
     scaleY = if @getActionProp 'flipY' then -1 else 1
@@ -267,6 +289,12 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
       angle = -angle if angle < 0
       angle = 180 - angle if angle > 90
       scaleX = 0.5 + 0.5 * (90 - angle) / 90
+      
+    if @isRaster # scale is worked into building the sprite sheet for animations
+      scale = @thangType.get('scale') or 1
+      scaleX *= scale
+      scaleY *= scale
+      
     scaleFactorX = @thang.scaleFactorX ? @scaleFactor
     scaleFactorY = @thang.scaleFactorY ? @scaleFactor
     @imageObject.scaleX = @originalScaleX * scaleX * scaleFactorX
@@ -319,6 +347,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
 
   ##################################################
   updateAction: ->
+    return if @isRaster
     action = @determineAction()
     isDifferent = action isnt @currentRootAction or action is null
     if not action and @thang?.actionActivated and not @stopLogging
@@ -440,10 +469,11 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
     def = x: 0, y: {registration: 0, torso: -50, mouth: -60, aboveHead: -100}[prop]
     pos = @getActionProp 'positions', prop, def
     pos = x: pos.x, y: pos.y
-    scale = @getActionProp 'scale', null, 1
-    scale *= @options.resolutionFactor if prop is 'registration'
-    pos.x *= scale
-    pos.y *= scale
+    if not @isRaster
+      scale = @getActionProp 'scale', null, 1
+      scale *= @options.resolutionFactor if prop is 'registration'
+      pos.x *= scale 
+      pos.y *= scale
     if @thang and prop isnt 'registration'
       scaleFactor = @thang.scaleFactor ? 1
       pos.x *= @thang.scaleFactorX ? scaleFactor
@@ -654,7 +684,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
       z = @shadow.pos.z
       @shadow.pos = pos
       @shadow.pos.z = z
-      @imageObject.gotoAndPlay(endAnimation)
+      @imageObject.gotoAndPlay?(endAnimation)
       return
 
     @shadow.action = 'move'
diff --git a/app/lib/surface/SpriteBoss.coffee b/app/lib/surface/SpriteBoss.coffee
index 1eb7aa8b7..63df6c789 100644
--- a/app/lib/surface/SpriteBoss.coffee
+++ b/app/lib/surface/SpriteBoss.coffee
@@ -145,7 +145,11 @@ module.exports = class SpriteBoss extends CocoClass
 
   addThangToSprites: (thang, layer=null) ->
     return console.warn 'Tried to add Thang to the surface it already has:', thang.id if @sprites[thang.id]
-    thangType = _.find @options.thangTypes, (m) -> m.get('actions') and m.get('name') is thang.spriteName
+    thangType = _.find @options.thangTypes, (m) ->
+      return false unless m.get('actions') or m.get('raster')
+      return m.get('name') is thang.spriteName
+    thangType ?= _.find @options.thangTypes, (m) -> return m.get('name') is thang.spriteName
+      
     options = @createSpriteOptions thang: thang
     options.resolutionFactor = if thangType.get('kind') is 'Floor' then 2 else 4
     sprite = new CocoSprite thangType, options
@@ -221,12 +225,12 @@ module.exports = class SpriteBoss extends CocoClass
   onCastSpells: -> @stop()
 
   play: ->
-    sprite.imageObject.play() for sprite in @spriteArray
+    sprite.imageObject.play?() for sprite in @spriteArray
     @selectionMark?.play()
     @targetMark?.play()
 
   stop: ->
-    sprite.imageObject.stop() for sprite in @spriteArray
+    sprite.imageObject.stop?() for sprite in @spriteArray
     @selectionMark?.stop()
     @targetMark?.stop()
 
diff --git a/app/models/ThangType.coffee b/app/models/ThangType.coffee
index 21cd1fadd..321f47990 100644
--- a/app/models/ThangType.coffee
+++ b/app/models/ThangType.coffee
@@ -210,8 +210,8 @@ module.exports = class ThangType extends CocoModel
       @tick = null
     stage
 
-  uploadGenericPortrait: (callback) ->
-    src = @getPortraitSource()
+  uploadGenericPortrait: (callback, src) ->
+    src ?= @getPortraitSource()
     return callback?() unless src
     src = src.replace('data:image/png;base64,', '').replace(/\ /g, '+')
     body =
diff --git a/app/schemas/models/thang_type.coffee b/app/schemas/models/thang_type.coffee
index eb78c1c11..37624c180 100644
--- a/app/schemas/models/thang_type.coffee
+++ b/app/schemas/models/thang_type.coffee
@@ -123,6 +123,7 @@ _.extend ThangTypeSchema.properties,
     title: 'Scale'
     type: 'number'
   positions: PositionsSchema
+  raster: { type: 'string', format: 'image-file', title: 'Raster Image' }
   colorGroups: c.object
     title: 'Color Groups'
     additionalProperties:
diff --git a/app/views/editor/thang/edit.coffee b/app/views/editor/thang/edit.coffee
index 4d1cc71e0..e728e438c 100644
--- a/app/views/editor/thang/edit.coffee
+++ b/app/views/editor/thang/edit.coffee
@@ -197,6 +197,7 @@ module.exports = class ThangTypeEditView extends View
   # animation select
 
   refreshAnimation: ->
+    return @showRasterImage() if @thangType.get('raster')
     options = @getSpriteOptions()
     @thangType.resetSpriteSheetCache()
     spriteSheet = @thangType.buildSpriteSheet(options)
@@ -207,6 +208,13 @@ module.exports = class ThangTypeEditView extends View
     @showAnimation()
     @updatePortrait()
 
+  showRasterImage: ->
+    sprite = new CocoSprite(@thangType, @getSpriteOptions())
+    @currentSprite?.destroy()
+    @currentSprite = sprite
+    @showDisplayObject(sprite.displayObject)
+    @updateScale()
+
   showAnimation: (animationName) ->
     animationName = @$el.find('#animations-select').val() unless _.isString animationName
     return unless animationName
@@ -310,8 +318,13 @@ module.exports = class ThangTypeEditView extends View
 
     res.success =>
       url = "/editor/thang/#{newThangType.get('slug') or newThangType.id}"
-      newThangType.uploadGenericPortrait ->
-        document.location.href = url
+      portraitSource = null
+      if @thangType.get('raster')
+        image = @currentSprite.imageObject.image
+        portraitSource = imageToPortrait image
+        # bit of a hacky way to get that portrait
+      success = -> document.location.href = url
+      newThangType.uploadGenericPortrait success, portraitSource
 
   clearRawData: ->
     @thangType.resetRawData()
@@ -393,3 +406,14 @@ module.exports = class ThangTypeEditView extends View
   destroy: ->
     @camera?.destroy()
     super()
+
+imageToPortrait = (img) ->
+  canvas = document.createElement("canvas")
+  canvas.width = 100
+  canvas.height = 100
+  ctx = canvas.getContext("2d")
+  scaleX = 100 / img.width
+  scaleY = 100 / img.height
+  ctx.scale scaleX, scaleY
+  ctx.drawImage img, 0, 0
+  canvas.toDataURL("image/png") 
\ No newline at end of file
diff --git a/app/views/play/level/hud_view.coffee b/app/views/play/level/hud_view.coffee
index 3b6f660af..70b2ffd73 100644
--- a/app/views/play/level/hud_view.coffee
+++ b/app/views/play/level/hud_view.coffee
@@ -112,16 +112,19 @@ module.exports = class HUDView extends View
     options = thang.getSpriteOptions() or {}
     options.async = false
     options.colorConfig = colorConfig if colorConfig
-    stage = thangType.getPortraitStage options
     wrapper = @$el.find '.thang-canvas-wrapper'
-    newCanvas = $(stage.canvas).addClass('thang-canvas')
-    wrapper.empty().append(newCanvas)
     team = @thang?.team or @speakerSprite?.thang?.team
     wrapper.removeClass (i, css) -> (css.match(/\bteam-\S+/g) or []).join ' '
     wrapper.addClass "team-#{team}"
-    stage.update()
-    @stage?.stopTalking()
-    @stage = stage
+    if thangType.get('raster')
+      wrapper.empty().append($('<img />').attr('src', '/file/'+thangType.get('raster')))
+    else
+      stage = thangType.getPortraitStage options
+      newCanvas = $(stage.canvas).addClass('thang-canvas')
+      wrapper.empty().append(newCanvas)
+      stage.update()
+      @stage?.stopTalking()
+      @stage = stage
 
   onThangBeganTalking: (e) ->
     return unless @stage and @thang is e.thang
diff --git a/server/levels/thangs/thang_type_handler.coffee b/server/levels/thangs/thang_type_handler.coffee
index abdecd529..a8d5c05e7 100644
--- a/server/levels/thangs/thang_type_handler.coffee
+++ b/server/levels/thangs/thang_type_handler.coffee
@@ -5,21 +5,22 @@ ThangTypeHandler = class ThangTypeHandler extends Handler
   modelClass: ThangType
   jsonSchema: require '../../../app/schemas/models/thang_type'
   editableProperties: [
-    'name',
-    'raw',
-    'actions',
-    'soundTriggers',
-    'rotationType',
-    'matchWorldDimensions',
-    'shadow',
-    'layerPriority',
-    'staticImage',
-    'scale',
-    'positions',
-    'snap',
-    'components',
-    'colorGroups',
+    'name'
+    'raw'
+    'actions'
+    'soundTriggers'
+    'rotationType'
+    'matchWorldDimensions'
+    'shadow'
+    'layerPriority'
+    'staticImage'
+    'scale'
+    'positions'
+    'snap'
+    'components'
+    'colorGroups'
     'kind'
+    'raster'
   ]
 
   hasAccess: (req) ->

From 73222a1a98d54d5080cf95621358fcc6d028dce1 Mon Sep 17 00:00:00 2001
From: Nick Winter <livelily@gmail.com>
Date: Sat, 3 May 2014 09:13:26 -0700
Subject: [PATCH 06/12] Don't start drawing Surface until needed.

---
 app/views/play/level_view.coffee | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/app/views/play/level_view.coffee b/app/views/play/level_view.coffee
index 56e219ec3..16ed16943 100644
--- a/app/views/play/level_view.coffee
+++ b/app/views/play/level_view.coffee
@@ -185,10 +185,6 @@ module.exports = class PlayLevelView extends View
     @levelLoader = null
     @initSurface()
     @initScriptManager()
-    @surface.showLevel()
-    if @otherSession
-      # TODO: colorize name and cloud by team, colorize wizard by user's color config
-      @surface.createOpponentWizard id: @otherSession.get('creator'), name: @otherSession.get('creatorName'), team: @otherSession.get('team')
 
   grabLevelLoaderData: ->
     @session = @levelLoader.session
@@ -215,6 +211,10 @@ module.exports = class PlayLevelView extends View
       @session.set 'multiplayer', false
 
   onLevelStarted: (e) ->
+    @surface.showLevel()
+    if @otherSession
+      # TODO: colorize name and cloud by team, colorize wizard by user's color config
+      @surface.createOpponentWizard id: @otherSession.get('creator'), name: @otherSession.get('creatorName'), team: @otherSession.get('team')
     @loadingView?.unveil()
 
   onLoadingViewUnveiled: (e) ->

From 1d88b6eefe9c8ff85b134335daa5a29b88312add Mon Sep 17 00:00:00 2001
From: Scott Erickson <sderickson@gmail.com>
Date: Tue, 13 May 2014 10:26:33 -0700
Subject: [PATCH 07/12] Most of the way there getting it to work with loading
 thang types dynamically throughout the play view.

---
 app/lib/surface/CocoSprite.coffee             |  4 +++-
 app/lib/surface/Layer.coffee                  |  1 +
 app/lib/surface/Mark.coffee                   |  4 ++--
 app/lib/surface/SpriteBoss.coffee             |  2 ++
 app/models/SuperModel.coffee                  |  2 +-
 app/models/ThangType.coffee                   | 12 +++++++++--
 app/views/play/level/hud_view.coffee          |  7 +++++++
 app/views/play/level/thang_avatar_view.coffee | 20 +++++++++++++++----
 8 files changed, 42 insertions(+), 10 deletions(-)

diff --git a/app/lib/surface/CocoSprite.coffee b/app/lib/surface/CocoSprite.coffee
index 493c8cd5e..f3a2327a2 100644
--- a/app/lib/surface/CocoSprite.coffee
+++ b/app/lib/surface/CocoSprite.coffee
@@ -70,7 +70,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
     @age = 0
     @scaleFactor = @targetScaleFactor = 1
     @displayObject = new createjs.Container()
-    if @thangType.get('actions') or @thangType.get('raster')
+    if @thangType.isFullyLoaded()
       @setupSprite()
     else
       @stillLoading = true
@@ -146,6 +146,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
 
   queueAction: (action) ->
     # The normal way to have an action play
+    return unless @thangType.isFullyLoaded()
     action = @actions[action] if _.isString(action)
     action ?= @actions.idle
     @actionQueue = []
@@ -266,6 +267,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
     @hasMoved = true
 
   updateScale: ->
+    return unless @imageObject
     if @thangType.get('matchWorldDimensions') and @thang
       if @thang.width isnt @lastThangWidth or @thang.height isnt @lastThangHeight
         bounds = @imageObject.getBounds()
diff --git a/app/lib/surface/Layer.coffee b/app/lib/surface/Layer.coffee
index a0f8d5ad4..1e438a2ea 100644
--- a/app/lib/surface/Layer.coffee
+++ b/app/lib/surface/Layer.coffee
@@ -101,4 +101,5 @@ module.exports = class Layer extends createjs.Container
   cache: ->
     return unless @children.length
     bounds = @getBounds()
+    return unless bounds
     super bounds.x, bounds.y, bounds.width, bounds.height, 2
diff --git a/app/lib/surface/Mark.coffee b/app/lib/surface/Mark.coffee
index 7c9abb91f..1b29aa375 100644
--- a/app/lib/surface/Mark.coffee
+++ b/app/lib/surface/Mark.coffee
@@ -200,7 +200,7 @@ module.exports = class Mark extends CocoClass
     Backbone.Mediator.publish 'sprite:loaded'
 
   update: (pos=null) ->
-    return false unless @on and @mark
+    return false unless @on and @mark and @sprite?.thangType.isFullyLoaded()
     @mark.visible = not @hidden
     @updatePosition pos
     @updateRotation()
@@ -242,7 +242,7 @@ module.exports = class Mark extends CocoClass
       oldMark.parent.removeChild oldMark
     return unless @name in ["selection", "target", "repair", "highlight"]
     scale = 0.5
-    if @sprite
+    if @sprite?.imageObject
       size = @sprite.getAverageDimension()
       size += 60 if @name is 'selection'
       size += 60 if @name is 'repair'
diff --git a/app/lib/surface/SpriteBoss.coffee b/app/lib/surface/SpriteBoss.coffee
index fce5c299d..ea3825e1e 100644
--- a/app/lib/surface/SpriteBoss.coffee
+++ b/app/lib/surface/SpriteBoss.coffee
@@ -201,6 +201,8 @@ module.exports = class SpriteBoss extends CocoClass
   cache: (update=false) ->
     return if @cached and not update
     wallSprites = (sprite for sprite in @spriteArray when sprite.thangType?.get('name').search(/(dungeon|indoor).wall/i) isnt -1)
+    unless _.all (s.thangType.isFullyLoaded() for s in wallSprites)
+      return 
     walls = (sprite.thang for sprite in wallSprites)
     @world.calculateBounds()
     wallGrid = new Grid walls, @world.size()...
diff --git a/app/models/SuperModel.coffee b/app/models/SuperModel.coffee
index 9d610447a..8541333b1 100644
--- a/app/models/SuperModel.coffee
+++ b/app/models/SuperModel.coffee
@@ -19,7 +19,7 @@ module.exports = class SuperModel extends Backbone.Model
   loadModel: (model, name, fetchOptions, value=1) ->
     cachedModel = @getModelByURL(model.getURL())
     if cachedModel
-      console.debug 'Model cache hit', cachedModel.getURL(), 'already loaded', cachedModel.loaded
+      console.debug 'Model cache hit', cachedModel.get('name') or cachedModel.getURL()
       if cachedModel.loaded
         res = @addModelResource(cachedModel, name, fetchOptions, 0)
         res.markLoaded()
diff --git a/app/models/ThangType.coffee b/app/models/ThangType.coffee
index 321f47990..e29ec3215 100644
--- a/app/models/ThangType.coffee
+++ b/app/models/ThangType.coffee
@@ -26,12 +26,18 @@ module.exports = class ThangType extends CocoModel
     @buildActions()
     @spriteSheets = {}
     @building = {}
+    
+  isFullyLoaded: ->
+    # TODO: Come up with a better way to identify when the model doesn't have everything needed to build the sprite. ie when it's a projection without all the required data.
+    return @get('actions') or @get('raster') # needs one of these two things
 
   getActions: ->
+    return {} unless @isFullyLoaded()
     return @actions or @buildActions()
 
   buildActions: ->
-    @actions = $.extend(true, {}, @get('actions') or {})
+    return null unless @isFullyLoaded()
+    @actions = $.extend(true, {}, @get('actions'))
     for name, action of @actions
       action.name = name
       for relatedName, relatedAction of action.relatedActions ? {}
@@ -52,6 +58,7 @@ module.exports = class ThangType extends CocoModel
     options
 
   buildSpriteSheet: (options) ->
+    return false unless @isFullyLoaded()
     @options = @fillOptions options
     key = @spriteSheetKey(@options)
     return if @building[key]
@@ -146,7 +153,7 @@ module.exports = class ThangType extends CocoModel
       return true
     t0 = new Date()
     spriteSheet = @builder.build()
-    console.warn "Built #{@get('name')} in #{new Date() - t0}ms on main thread."
+    console.debug "Built #{@get('name')} in #{new Date() - t0}ms."
     @spriteSheets[key] = spriteSheet
     delete @building[key]
     spriteSheet
@@ -180,6 +187,7 @@ module.exports = class ThangType extends CocoModel
     stage?.toDataURL()
 
   getPortraitStage: (spriteOptionsOrKey, size=100) ->
+    return unless @isFullyLoaded()
     key = spriteOptionsOrKey
     key = if _.isString(key) then key else @spriteSheetKey(@fillOptions(key))
     spriteSheet = @spriteSheets[key]
diff --git a/app/views/play/level/hud_view.coffee b/app/views/play/level/hud_view.coffee
index deeab1f04..87625d631 100644
--- a/app/views/play/level/hud_view.coffee
+++ b/app/views/play/level/hud_view.coffee
@@ -109,6 +109,13 @@ module.exports = class HUDView extends View
     @update()
 
   createAvatar: (thangType, thang, colorConfig) ->
+    unless thangType.isFullyLoaded()
+      args = arguments
+      unless @listeningToCreateAvatar
+        @listenToOnce thangType, 'sync', -> @createAvatar(args...)
+        @listeningToCreateAvatar = true
+      return
+    @listeningToCreateAvatar = false
     options = thang.getSpriteOptions() or {}
     options.async = false
     options.colorConfig = colorConfig if colorConfig
diff --git a/app/views/play/level/thang_avatar_view.coffee b/app/views/play/level/thang_avatar_view.coffee
index 3c22fc5cb..85e485bac 100644
--- a/app/views/play/level/thang_avatar_view.coffee
+++ b/app/views/play/level/thang_avatar_view.coffee
@@ -14,16 +14,28 @@ module.exports = class ThangAvatarView extends View
     super options
     @thang = options.thang
     @includeName = options.includeName
+    @thangType = @getSpriteThangType()
+    if not @thangType
+      console.error 'Thang avatar view expected a thang type to be provided.'
+      return
+      
+    unless @thangType.isFullyLoaded() or @thangType.loading
+      @thangType.fetch()
+    
+    @supermodel.loadModel @thangType, 'thang'
+
+  getSpriteThangType: ->
+    thangs = @supermodel.getModels(ThangType)
+    thangs = (t for t in thangs when t.get('name') is @thang.spriteName)
+    loadedThangs = (t for t in thangs when t.isFullyLoaded())
+    return loadedThangs[0] or thangs[0] # try to return one with all the goods, otherwise a projection
 
   getRenderData: (context={}) ->
     context = super context
     context.thang = @thang
-    thangs = @supermodel.getModels(ThangType)
-    thangs = (t for t in thangs when t.get('name') is @thang.spriteName)
-    thang = thangs[0]
     options = @thang?.getSpriteOptions() or {}
     options.async = false
-    context.avatarURL = thang.getPortraitSource(options)
+    context.avatarURL = @thangType.getPortraitSource(options)
     context.includeName = @includeName
     context
 

From b4ad34eb8ffb126dca05b2f0ca551320087ae1ca Mon Sep 17 00:00:00 2001
From: Scott Erickson <sderickson@gmail.com>
Date: Tue, 13 May 2014 10:51:55 -0700
Subject: [PATCH 08/12] LevelLoader only builds and loads thang types in the
 world on the first frame, letting everything else happen lazily.

---
 app/lib/LevelLoader.coffee | 19 ++++++++++++-------
 1 file changed, 12 insertions(+), 7 deletions(-)

diff --git a/app/lib/LevelLoader.coffee b/app/lib/LevelLoader.coffee
index c7abdb3aa..dced1ec67 100644
--- a/app/lib/LevelLoader.coffee
+++ b/app/lib/LevelLoader.coffee
@@ -108,8 +108,8 @@ module.exports = class LevelLoader extends CocoClass
     worldNecessities = []
 
     @thangIDs = _.uniq thangIDs
-    thangNames = new ThangNamesCollection(@thangIDs)
-    worldNecessities.push @supermodel.loadCollection(thangNames, 'thang_names')
+    @thangNames = new ThangNamesCollection(@thangIDs)
+    worldNecessities.push @supermodel.loadCollection(@thangNames, 'thang_names')
 
     for obj in objUniq componentVersions
       url = "/db/level.component/#{obj.original}/version/#{obj.majorVersion}"
@@ -133,11 +133,16 @@ module.exports = class LevelLoader extends CocoClass
 
   onWorldNecessitiesLoaded: =>
     @initWorld()
-    for thangID in @thangIDs
-      url = "/db/thang.type/#{thangID}/version"
-      url += "?project=true" if @headless and not @editorMode
-      res = @maybeLoadURL url, ThangType, 'thang'
-      @listenToOnce res.model, 'sync', @buildSpriteSheetsForThangType if res
+    return if @headless and not @editorMode
+    thangsToLoad = _.uniq( (t.spriteName for t in @world.thangs) )
+    nameModelTuples = ([thangType.get('name'), thangType] for thangType in @thangNames.models)
+    nameModelMap = _.zipObject nameModelTuples
+
+    for thangTypeName in thangsToLoad
+      thang = nameModelMap[thangTypeName]
+      thang.fetch()
+      thang = @supermodel.loadModel(thang, 'thang').model
+      @listenToOnce thang, 'sync', @buildSpriteSheetsForThangType
 
   maybeLoadURL: (url, Model, resourceName) ->
     return if @supermodel.getModel(url)

From 4b404ae1248754d832a29c39a5f59c8484908c31 Mon Sep 17 00:00:00 2001
From: Scott Erickson <sderickson@gmail.com>
Date: Tue, 13 May 2014 14:39:45 -0700
Subject: [PATCH 09/12] Finished tuning the supermodel loading and progress.

---
 app/lib/LevelLoader.coffee                    | 31 ++++++++++++++++---
 app/locale/en.coffee                          |  1 +
 app/models/SuperModel.coffee                  |  3 +-
 app/models/ThangType.coffee                   |  5 +--
 app/views/play/level/thang_avatar_view.coffee |  2 +-
 5 files changed, 33 insertions(+), 9 deletions(-)

diff --git a/app/lib/LevelLoader.coffee b/app/lib/LevelLoader.coffee
index dced1ec67..911ea698c 100644
--- a/app/lib/LevelLoader.coffee
+++ b/app/lib/LevelLoader.coffee
@@ -137,12 +137,18 @@ module.exports = class LevelLoader extends CocoClass
     thangsToLoad = _.uniq( (t.spriteName for t in @world.thangs) )
     nameModelTuples = ([thangType.get('name'), thangType] for thangType in @thangNames.models)
     nameModelMap = _.zipObject nameModelTuples
+    @spriteSheetsToBuild = []
 
     for thangTypeName in thangsToLoad
-      thang = nameModelMap[thangTypeName]
-      thang.fetch()
-      thang = @supermodel.loadModel(thang, 'thang').model
-      @listenToOnce thang, 'sync', @buildSpriteSheetsForThangType
+      thangType = nameModelMap[thangTypeName]
+      thangType.fetch()
+      thangType = @supermodel.loadModel(thangType, 'thang').model
+      res = @supermodel.addSomethingResource "sprite_sheet", 5
+      res.thangType = thangType
+      res.markLoading()
+      @spriteSheetsToBuild.push res
+
+    @buildLoopInterval = setInterval @buildLoop, 5
 
   maybeLoadURL: (url, Model, resourceName) ->
     return if @supermodel.getModel(url)
@@ -150,9 +156,22 @@ module.exports = class LevelLoader extends CocoClass
     @supermodel.loadModel(model, resourceName)
 
   onSupermodelLoaded: ->
+    console.log 'SuperModel for Level loaded in', new Date().getTime() - @t0, 'ms'
     @loadLevelSounds()
     @denormalizeSession()
     app.tracker.updatePlayState(@level, @session) unless @headless
+    
+  buildLoop: =>
+    return if @lastBuilt and new Date().getTime() - @lastBuilt < 10
+    return clearInterval @buildLoopInterval unless @spriteSheetsToBuild.length
+      
+    for spriteSheetResource, i in @spriteSheetsToBuild
+      if spriteSheetResource.thangType.loaded
+        @buildSpriteSheetsForThangType spriteSheetResource.thangType
+        @spriteSheetsToBuild.splice i, 1
+        @lastBuilt = new Date().getTime()
+        spriteSheetResource.markLoaded()
+        return
 
   denormalizeSession: ->
     return if @headless or @sessionDenormalized or @spectateMode
@@ -246,3 +265,7 @@ module.exports = class LevelLoader extends CocoClass
   # everything else sound wise is loaded as needed as worlds are generated
 
   progress: -> @supermodel.progress
+    
+  destroy: ->
+    clearInterval @buildLoopInterval if @buildLoopInterval
+    super()
diff --git a/app/locale/en.coffee b/app/locale/en.coffee
index a41ebff08..5c10f0a1c 100644
--- a/app/locale/en.coffee
+++ b/app/locale/en.coffee
@@ -715,6 +715,7 @@
     top_simulators: "Top Simulators"
     source_document: "Source Document"
     document: "Document" # note to diplomats: not a physical document, a document in MongoDB, ie a record in a database
+    sprite_sheet: "Sprite Sheet"
 
   delta:
     added: "Added"
diff --git a/app/models/SuperModel.coffee b/app/models/SuperModel.coffee
index 8541333b1..9545d2a4e 100644
--- a/app/models/SuperModel.coffee
+++ b/app/models/SuperModel.coffee
@@ -19,7 +19,6 @@ module.exports = class SuperModel extends Backbone.Model
   loadModel: (model, name, fetchOptions, value=1) ->
     cachedModel = @getModelByURL(model.getURL())
     if cachedModel
-      console.debug 'Model cache hit', cachedModel.get('name') or cachedModel.getURL()
       if cachedModel.loaded
         res = @addModelResource(cachedModel, name, fetchOptions, 0)
         res.markLoaded()
@@ -141,7 +140,7 @@ module.exports = class SuperModel extends Backbone.Model
     @listenToOnce(resource, 'loaded', @onResourceLoaded)
     @listenTo(resource, 'failed', @onResourceFailed)
     @denom += value
-    @updateProgress() if @denom
+    _.defer @updateProgress if @denom
 
   onResourceLoaded: (r) ->
     @num += r.value
diff --git a/app/models/ThangType.coffee b/app/models/ThangType.coffee
index e29ec3215..345bc0264 100644
--- a/app/models/ThangType.coffee
+++ b/app/models/ThangType.coffee
@@ -61,7 +61,9 @@ module.exports = class ThangType extends CocoModel
     return false unless @isFullyLoaded()
     @options = @fillOptions options
     key = @spriteSheetKey(@options)
+    if ss = @spriteSheets[key] then return ss
     return if @building[key]
+    @t0 = new Date().getTime()
     @initBuild(options)
     @addGeneralFrames() unless @options.portraitOnly
     @addPortrait()
@@ -151,9 +153,8 @@ module.exports = class ThangType extends CocoModel
       @builder.buildAsync() unless buildQueue.length > 1
       @builder.on 'complete', @onBuildSpriteSheetComplete, @, true, key
       return true
-    t0 = new Date()
     spriteSheet = @builder.build()
-    console.debug "Built #{@get('name')} in #{new Date() - t0}ms."
+    console.debug "Built #{@get('name')} in #{new Date().getTime() - @t0}ms."
     @spriteSheets[key] = spriteSheet
     delete @building[key]
     spriteSheet
diff --git a/app/views/play/level/thang_avatar_view.coffee b/app/views/play/level/thang_avatar_view.coffee
index 85e485bac..a1af2102b 100644
--- a/app/views/play/level/thang_avatar_view.coffee
+++ b/app/views/play/level/thang_avatar_view.coffee
@@ -35,7 +35,7 @@ module.exports = class ThangAvatarView extends View
     context.thang = @thang
     options = @thang?.getSpriteOptions() or {}
     options.async = false
-    context.avatarURL = @thangType.getPortraitSource(options)
+    context.avatarURL = @thangType.getPortraitSource(options) unless @thangType.loading
     context.includeName = @includeName
     context
 

From 54af58baeeaa3d6ef22255fe08a00d0620b46533 Mon Sep 17 00:00:00 2001
From: Scott Erickson <sderickson@gmail.com>
Date: Wed, 14 May 2014 09:24:52 -0700
Subject: [PATCH 10/12] SuperModel no longer emits progress going backward
 ever, and added a way to prevent it from getting to high in the middle of a
 chain of loading.

---
 app/lib/LevelLoader.coffee   |  2 ++
 app/models/SuperModel.coffee | 11 +++++++++--
 2 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/app/lib/LevelLoader.coffee b/app/lib/LevelLoader.coffee
index 911ea698c..61e0a7918 100644
--- a/app/lib/LevelLoader.coffee
+++ b/app/lib/LevelLoader.coffee
@@ -25,6 +25,7 @@ module.exports = class LevelLoader extends CocoClass
     @t0 = new Date().getTime()
     super()
     @supermodel = options.supermodel
+    @supermodel.setMaxProgress 0.2
     @levelID = options.levelID
     @sessionID = options.sessionID
     @opponentSessionID = options.opponentSessionID
@@ -133,6 +134,7 @@ module.exports = class LevelLoader extends CocoClass
 
   onWorldNecessitiesLoaded: =>
     @initWorld()
+    @supermodel.clearMaxProgress()
     return if @headless and not @editorMode
     thangsToLoad = _.uniq( (t.spriteName for t in @world.thangs) )
     nameModelTuples = ([thangType.get('name'), thangType] for thangType in @thangNames.models)
diff --git a/app/models/SuperModel.coffee b/app/models/SuperModel.coffee
index 9545d2a4e..ef68d39c9 100644
--- a/app/models/SuperModel.coffee
+++ b/app/models/SuperModel.coffee
@@ -5,6 +5,7 @@ module.exports = class SuperModel extends Backbone.Model
     @progress = 0
     @resources = {}
     @rid = 0
+    @maxProgress = 1
 
     @models = {}
     @collections = {}
@@ -154,11 +155,17 @@ module.exports = class SuperModel extends Backbone.Model
     # a bunch of things load all at once.
     # So make sure we only emit events if @progress has changed.
     newProg = if @denom then @num / @denom else 1
-    return if @progress is newProg
+    newProg = Math.min @maxProgress, newProg
+    return if @progress >= newProg
     @progress = newProg
     @trigger('update-progress', @progress)
     @trigger('loaded-all') if @finished()
-
+    
+  setMaxProgress: (@maxProgress) ->
+  clearMaxProgress: ->
+    @maxProgress = 1
+    _.defer @updateProgress
+    
   getProgress: -> return @progress
 
   getResource: (rid) ->

From 0be813e42499d29e43096d55248a6a47fede179e Mon Sep 17 00:00:00 2001
From: Scott Erickson <sderickson@gmail.com>
Date: Wed, 14 May 2014 10:35:16 -0700
Subject: [PATCH 11/12] Figured out how to get the world to properly get shown
 if the world is created before the level loader is completely finished.

---
 app/lib/surface/Surface.coffee   |  2 ++
 app/views/play/level_view.coffee | 10 +++-------
 2 files changed, 5 insertions(+), 7 deletions(-)

diff --git a/app/lib/surface/Surface.coffee b/app/lib/surface/Surface.coffee
index 506626c51..62f0901b2 100644
--- a/app/lib/surface/Surface.coffee
+++ b/app/lib/surface/Surface.coffee
@@ -84,6 +84,8 @@ module.exports = Surface = class Surface extends CocoClass
     @initAudio()
     @onResize = _.debounce @onResize, 500
     $(window).on 'resize', @onResize
+    if @world.ended
+      _.defer => @setWorld @world
 
   destroy: ->
     @dead = true
diff --git a/app/views/play/level_view.coffee b/app/views/play/level_view.coffee
index e005c1948..af874a04b 100644
--- a/app/views/play/level_view.coffee
+++ b/app/views/play/level_view.coffee
@@ -60,7 +60,6 @@ module.exports = class PlayLevelView extends View
     'surface:world-set-up': 'onSurfaceSetUpNewWorld'
     'level:session-will-save': 'onSessionWillSave'
     'level:set-team': 'setTeam'
-    'god:new-world-created': 'loadSoundsForWorld'
     'level:started': 'onLevelStarted'
     'level:loading-view-unveiled': 'onLoadingViewUnveiled'
 
@@ -309,9 +308,6 @@ module.exports = class PlayLevelView extends View
     $('#level-done-button', @$el).hide()
     application.tracker?.trackEvent 'Confirmed Restart', level: @world.name, label: @world.name
 
-  onNewWorld: (e) ->
-    @world = e.world
-
   onInfiniteLoop: (e) ->
     return unless e.firstWorld
     @openModalView new InfiniteLoopModal()
@@ -484,11 +480,11 @@ module.exports = class PlayLevelView extends View
 
   # Dynamic sound loading
 
-  loadSoundsForWorld: (e) ->
+  onNewWorld: (e) ->
     return if @headless
-    world = e.world
+    @world = e.world
     thangTypes = @supermodel.getModels(ThangType)
-    for [spriteName, message] in world.thangDialogueSounds()
+    for [spriteName, message] in @world.thangDialogueSounds()
       continue unless thangType = _.find thangTypes, (m) -> m.get('name') is spriteName
       continue unless sound = AudioPlayer.soundForDialogue message, thangType.get('soundTriggers')
       AudioPlayer.preloadSoundReference sound

From 09fbfdb360de079af155bc793a282a99cbcfddda Mon Sep 17 00:00:00 2001
From: Scott Erickson <sderickson@gmail.com>
Date: Wed, 14 May 2014 11:13:36 -0700
Subject: [PATCH 12/12] Fixed a bug with the simulator due to the changes made
 to the supermodel.

---
 app/lib/simulator/Simulator.coffee | 2 +-
 app/models/SuperModel.coffee       | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/app/lib/simulator/Simulator.coffee b/app/lib/simulator/Simulator.coffee
index 8c4d9df17..b89677512 100644
--- a/app/lib/simulator/Simulator.coffee
+++ b/app/lib/simulator/Simulator.coffee
@@ -74,7 +74,7 @@ module.exports = class Simulator extends CocoClass
       return
 
     @supermodel ?= new SuperModel()
-
+    @supermodel.resetProgress()
     @levelLoader = new LevelLoader supermodel: @supermodel, levelID: levelID, sessionID: @task.getFirstSessionID(), headless: true
     if @supermodel.finished()
       @simulateGame()
diff --git a/app/models/SuperModel.coffee b/app/models/SuperModel.coffee
index ef68d39c9..cbfc1d2d2 100644
--- a/app/models/SuperModel.coffee
+++ b/app/models/SuperModel.coffee
@@ -162,6 +162,7 @@ module.exports = class SuperModel extends Backbone.Model
     @trigger('loaded-all') if @finished()
     
   setMaxProgress: (@maxProgress) ->
+  resetProgress: -> @progress = 0
   clearMaxProgress: ->
     @maxProgress = 1
     _.defer @updateProgress