diff --git a/app/assets/javascripts/workers/aether_worker.js b/app/assets/javascripts/workers/aether_worker.js
index 65badb459..a75469a29 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/lodash.js", "/javascripts/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/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
new file mode 100644
index 000000000..414628d84
--- /dev/null
+++ b/app/collections/ThangNamesCollection.coffee
@@ -0,0 +1,14 @@
+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) -> 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 5d539eae4..61e0a7918 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,8 +22,10 @@ World = require 'lib/world/world'
 module.exports = class LevelLoader extends CocoClass
 
   constructor: (options) ->
+    @t0 = new Date().getTime()
     super()
     @supermodel = options.supermodel
+    @supermodel.setMaxProgress 0.2
     @levelID = options.levelID
     @sessionID = options.sessionID
     @opponentSessionID = options.opponentSessionID
@@ -103,17 +106,18 @@ module.exports = class LevelLoader extends CocoClass
 
     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
+    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'
@@ -125,16 +129,51 @@ module.exports = class LevelLoader extends CocoClass
       wizard = ThangType.loadUniversalWizard()
       @supermodel.loadModel wizard, 'thang'
 
+    jqxhrs = (resource.jqxhr for resource in worldNecessities when resource?.jqxhr)
+    $.when(jqxhrs...).done(@onWorldNecessitiesLoaded)
+
+  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)
+    nameModelMap = _.zipObject nameModelTuples
+    @spriteSheetsToBuild = []
+
+    for thangTypeName in thangsToLoad
+      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)
     model = new Model().setURL url
     @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
-    @initWorld()
+    
+  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
@@ -156,6 +195,10 @@ module.exports = class LevelLoader extends CocoClass
 
   buildSpriteSheetsForThangType: (thangType) ->
     return if @headless
+    # 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}
@@ -198,6 +241,7 @@ module.exports = class LevelLoader extends CocoClass
     @world = new World()
     serializedLevel = @level.serialize(@supermodel)
     @world.loadFromLevel serializedLevel, false
+    console.log "World has been initialized from level loader."
 
   # Initial Sound Loading
 
@@ -223,3 +267,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/lib/simulator/Simulator.coffee b/app/lib/simulator/Simulator.coffee
index ff549797f..e8b326b49 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/lib/surface/CocoSprite.coffee b/app/lib/surface/CocoSprite.coffee
index 225c6b308..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')
+    if @thangType.isFullyLoaded()
       @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
@@ -126,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 = []
@@ -143,6 +164,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()
@@ -245,15 +267,17 @@ 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
-        [@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
@@ -270,6 +294,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
@@ -322,6 +352,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
@@ -443,10 +474,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
@@ -658,7 +690,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/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 8cc3bcfa7..a9b26972f 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 df3642e83..bda6e1992 100644
--- a/app/lib/surface/SpriteBoss.coffee
+++ b/app/lib/surface/SpriteBoss.coffee
@@ -47,7 +47,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 = {}
@@ -144,7 +144,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('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
@@ -196,6 +200,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/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/locale/en.coffee b/app/locale/en.coffee
index b8c3d7707..5c10f0a1c 100644
--- a/app/locale/en.coffee
+++ b/app/locale/en.coffee
@@ -699,6 +699,7 @@
     user_schema: "User Schema"
     user_profile: "User Profile"
     patches: "Patches"
+    patched_model: "Source Document"
     model: "Model"
     system: "System"
     component: "Component"
@@ -709,10 +710,12 @@
     opponent_session: "Opponent Session"
     article: "Article"
     user_names: "User Names"
+    thang_names: "Thang Names"
     files: "Files"
     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/CocoModel.coffee b/app/models/CocoModel.coffee
index 75d0172dd..35f0318ed 100644
--- a/app/models/CocoModel.coffee
+++ b/app/models/CocoModel.coffee
@@ -37,6 +37,8 @@ class CocoModel extends Backbone.Model
     @loading = false
     @markToRevert()
     @loadFromBackup()
+    
+  getNormalizedURL: -> "#{@urlRoot}/#{@id}"
 
   set: ->
     res = super(arguments...)
@@ -76,9 +78,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 465e2b17b..cbfc1d2d2 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 = {}
@@ -19,7 +20,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.getURL(), 'already loaded', cachedModel.loaded
       if cachedModel.loaded
         res = @addModelResource(cachedModel, name, fetchOptions, 0)
         res.markLoaded()
@@ -96,7 +96,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())
@@ -141,7 +141,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
@@ -155,11 +155,18 @@ 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) ->
+  resetProgress: -> @progress = 0
+  clearMaxProgress: ->
+    @maxProgress = 1
+    _.defer @updateProgress
+    
   getProgress: -> return @progress
 
   getResource: (rid) ->
@@ -202,6 +209,7 @@ class ModelResource extends Resource
     super(name, value)
     @model = modelOrCollection
     @fetchOptions = fetchOptions
+    @jqxhr = @model.jqxhr
 
   load: ->
     @markLoading()
diff --git a/app/models/ThangType.coffee b/app/models/ThangType.coffee
index 21cd1fadd..345bc0264 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,9 +58,12 @@ module.exports = class ThangType extends CocoModel
     options
 
   buildSpriteSheet: (options) ->
+    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()
@@ -144,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.warn "Built #{@get('name')} in #{new Date() - t0}ms on main thread."
+    console.debug "Built #{@get('name')} in #{new Date().getTime() - @t0}ms."
     @spriteSheets[key] = spriteSheet
     delete @building[key]
     spriteSheet
@@ -180,6 +188,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]
@@ -210,8 +219,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/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 be8c4dde7..4603cbc48 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'
@@ -22,7 +22,7 @@ module.exports = class PatchModal extends ModalView
     else
       @originalSource = new @targetModel.constructor({_id:targetID})
       @supermodel.loadModel @originalSource, 'source_document'
-      
+
   getRenderData: ->
     c = super()
     c.isPatchCreator = @patch.get('creator') is auth.me.id
@@ -30,7 +30,7 @@ module.exports = class PatchModal extends ModalView
     c.status = @patch.get 'status'
     c.patch = @patch
     c
-    
+
   afterRender: ->
     return unless @supermodel.finished()
     headModel = null
@@ -38,7 +38,7 @@ module.exports = class PatchModal extends ModalView
       headModel = @originalSource.clone(false)
       headModel.set(@targetModel.attributes)
       headModel.loaded = true
-    
+
     pendingModel = @originalSource.clone(false)
     pendingModel.applyDelta(@patch.get('delta'))
     pendingModel.loaded = true
@@ -47,18 +47,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 396106e86..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,16 +16,16 @@ module.exports = class PatchesView extends CocoView
   constructor: (@model, options) ->
     super(options)
     @initPatches()
-    
+
   initPatches: ->
     @startedLoading = false
     @patches = new PatchesCollection([], {}, @model, @status)
-    
+
   load: ->
     @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
@@ -37,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/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 16576b334..87625d631 100644
--- a/app/views/play/level/hud_view.coffee
+++ b/app/views/play/level/hud_view.coffee
@@ -109,19 +109,29 @@ 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
-    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/app/views/play/level/thang_avatar_view.coffee b/app/views/play/level/thang_avatar_view.coffee
index 3c22fc5cb..a1af2102b 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) unless @thangType.loading
     context.includeName = @includeName
     context
 
diff --git a/app/views/play/level/tome/spell_view.coffee b/app/views/play/level/tome/spell_view.coffee
index e8b1e1078..4f8581d15 100644
--- a/app/views/play/level/tome/spell_view.coffee
+++ b/app/views/play/level/tome/spell_view.coffee
@@ -67,7 +67,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 72c88f9c6..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'
 
@@ -83,7 +82,6 @@ module.exports = class PlayLevelView extends View
     @sessionID = @getQueryVariable 'session'
 
     $(window).on('resize', @onWindowResize)
-    @listenToOnce(@supermodel, 'error', @onLevelLoadError)
     @saveScreenshot = _.throttle @saveScreenshot, 30000
 
     if @isEditorPreview
@@ -102,7 +100,6 @@ module.exports = class PlayLevelView extends View
     @supermodel.models = givenSupermodel.models
     @supermodel.collections = givenSupermodel.collections
     @supermodel.shouldSaveBackups = givenSupermodel.shouldSaveBackups
-
     @god?.level = @level.serialize @supermodel
     if @world
       serializedLevel = @level.serialize(@supermodel)
@@ -133,6 +130,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')
@@ -144,6 +144,21 @@ module.exports = class PlayLevelView extends View
       return unless article.loaded
     @showGuide()
 
+  onWorldInitialized: ->
+    @worldInitialized = true
+    team = @getQueryVariable("team") ? @world.teamForPlayer(0)
+    @loadOpponentTeam(team)
+    @god.setLevel @level.serialize @supermodel
+    @god.setWorldClassMap @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'
@@ -165,33 +180,16 @@ 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.setLevel @level.serialize @supermodel
-    @god.setWorldClassMap @world.classMap
-    @setTeam team
+    @levelLoader.destroy()
+    @levelLoader = null
     @initSurface()
-    @initGoalManager()
     @initScriptManager()
-    @insertSubviews()
-    @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
-      @surface.createOpponentWizard id: @otherSession.get('creator'), name: @otherSession.get('creatorName'), team: @otherSession.get('team')
 
   grabLevelLoaderData: ->
     @session = @levelLoader.session
     @world = @levelLoader.world
     @level = @levelLoader.level
     @otherSession = @levelLoader.opponentSession
-    @levelLoader.destroy()
-    @levelLoader = null
 
   loadOpponentTeam: (myTeam) ->
     opponentSpells = []
@@ -212,6 +210,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) ->
@@ -306,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()
@@ -481,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
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) ->
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) ->