diff --git a/.gitignore b/.gitignore
index c3df101ed..44a78773d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,6 +28,9 @@ Thumbs.db
 *.sublime-project
 *.sublime-workspace
 
+# IntelliJ/WebStorm
+*.iml
+
 # NPM packages folder.
 node_modules/
 bower_components/
@@ -77,4 +80,10 @@ bin/mongo/
 # windows
 /SCOCODE.bat
 
-### If you add something here, copy it to the end of .npmignore, too. ###
\ No newline at end of file
+# local settings
+login.coffee
+
+# debugging
+*.heapsnapshot
+
+### If you add something here, copy it to the end of .npmignore, too. ###
diff --git a/.npmignore b/.npmignore
index ae193b37d..5d0542980 100644
--- a/.npmignore
+++ b/.npmignore
@@ -53,6 +53,9 @@ Thumbs.db
 *.sublime-project
 *.sublime-workspace
 
+# IntelliJ/WebStorm
+*.iml
+
 # NPM packages folder.
 node_modules/
 
@@ -89,6 +92,12 @@ mongo/
 bin/node/
 bin/mongo/
 
-
 # Karma coverage
 coverage/
+
+# local settings
+login.coffee
+
+# debugging
+*.heapsnapshot
+
diff --git a/app/assets/javascripts/workers/worker_world.js b/app/assets/javascripts/workers/worker_world.js
index 45c5e80d7..97fea2e1d 100644
--- a/app/assets/javascripts/workers/worker_world.js
+++ b/app/assets/javascripts/workers/worker_world.js
@@ -14,8 +14,8 @@ if (!Function.prototype.bind) {
       throw new TypeError("Function.prototype.bind (Shim) - target is not callable");
     }
 
-    var aArgs = Array.prototype.slice.call(arguments, 1), 
-        fToBind = this, 
+    var aArgs = Array.prototype.slice.call(arguments, 1),
+        fToBind = this,
         fNOP = function () {},
         fBound = function () {
           return fToBind.apply(this instanceof fNOP && oThis
@@ -83,13 +83,277 @@ self.transferableSupported = function transferableSupported() {
 var World = self.require('lib/world/world');
 var GoalManager = self.require('lib/world/GoalManager');
 
+Aether.addGlobal('Vector', require('lib/world/vector'));
+Aether.addGlobal('_', _);
+
+var serializedClasses = {
+    "Thang": self.require('lib/world/thang'),
+    "Vector": self.require('lib/world/vector'),
+    "Rectangle": self.require('lib/world/rectangle')
+};
+self.currentUserCodeMapCopy = "";
+self.currentDebugWorldFrame = 0;
+
+self.stringifyValue = function(value, depth) {
+    var brackets, i, isArray, isObject, key, prefix, s, sep, size, v, values, _i, _j, _len, _len1, _ref, _ref1, _ref2, _ref3;
+    if (!value || _.isString(value)) {
+        return value;
+    }
+    if (_.isFunction(value)) {
+        if (depth === 2) {
+            return void 0;
+        } else {
+            return "<Function>";
+        }
+    }
+    if (value === this.thang && depth) {
+        return "<this " + value.id + ">";
+    }
+    if (depth === 2) {
+        if (((_ref = value.constructor) != null ? _ref.className : void 0) === "Thang") {
+            value = "<" + (value.type || value.spriteName) + " - " + value.id + ", " + (value.pos ? value.pos.toString() : 'non-physical') + ">";
+        } else {
+            value = value.toString();
+        }
+        return value;
+    }
+    isArray = _.isArray(value);
+    isObject = _.isObject(value);
+    if (!(isArray || isObject)) {
+        return value.toString();
+    }
+    brackets = isArray ? ["[", "]"] : ["{", "}"];
+    size = _.size(value);
+    if (!size) {
+        return brackets.join("");
+    }
+    values = [];
+    if (isArray) {
+        for (_i = 0, _len = value.length; _i < _len; _i++) {
+            v = value[_i];
+            s = this.stringifyValue(v, depth + 1);
+            if (s !== void 0) {
+                values.push("" + s);
+            }
+        }
+    } else {
+        _ref2 = (_ref1 = value.apiProperties) != null ? _ref1 : _.keys(value);
+        for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {
+            key = _ref2[_j];
+            if (key[0] === "_") continue;
+            s = this.stringifyValue(value[key], depth + 1);
+            if (s !== void 0) {
+                values.push(key + ": " + s);
+            }
+        }
+    }
+    sep = '\n' + ((function() {
+        var _k, _results;
+        _results = [];
+        for (i = _k = 0; 0 <= depth ? _k < depth : _k > depth; i = 0 <= depth ? ++_k : --_k) {
+            _results.push("  ");
+        }
+        return _results;
+    })()).join('');
+    prefix = (_ref3 = value.constructor) != null ? _ref3.className : void 0;
+    if (isArray) {
+        if (prefix == null) {
+            prefix = "Array";
+        }
+    }
+    if (isObject) {
+        if (prefix == null) {
+            prefix = "Object";
+        }
+    }
+    prefix = prefix ? prefix + " " : "";
+    return "" + prefix + brackets[0] + sep + "  " + (values.join(sep + '  ')) + sep + brackets[1];
+};
+
+var cache = {};
+
+self.invalidateCache = function () {
+    cache = {};
+};
+
+self.retrieveValueFromCache = function (thangID, spellID, variableChain, frame) {
+    var frameCache, thangCache, spellCache;
+    if ((frameCache = cache[frame]) && (thangCache = frameCache[thangID]) && (spellCache = thangCache[spellID]))
+        return spellCache[variableChain.join()];
+    return undefined;
+};
+
+
+self.updateCache = function (thangID, spellID, variableChain, frame, value) {
+    var key, keys, currentObject;
+    keys = [frame,thangID, spellID, variableChain.join()];
+    currentObject = cache;
+
+    for (var i = 0, len = keys.length - 1; i < len; i++)
+    {
+        key = keys[i];
+        if (!(key in currentObject))
+            currentObject[key] = {};
+        currentObject = currentObject[key];
+    }
+    currentObject[keys[keys.length - 1]] = value;
+};
+self.retrieveValueFromFrame = function retrieveValueFromFrame(args) {
+    var cacheValue;
+    if (args.frame === self.currentDebugWorldFrame && (cacheValue = self.retrieveValueFromCache(args.currentThangID, args.currentSpellID, args.variableChain, args.frame)))
+        return self.postMessage({type: 'debug-value-return', serialized: {"key": args.variableChain.join("."), "value": cacheValue}});
+
+
+    var retrieveProperty = function retrieveProperty(currentThangID, currentSpellID, variableChain)
+    {
+        var prop;
+        var value;
+        var keys = [];
+        for (var i = 0, len = variableChain.length; i < len; i++) {
+            prop = variableChain[i];
+            if (prop === "this")
+            {
+                value = self.debugWorld.thangMap[currentThangID];
+
+            }
+            else if (i === 0)
+            {
+                try
+                {
+                    var flowStates = self.debugWorld.userCodeMap[currentThangID][currentSpellID].flow.states;
+                    //we have to go to the second last flowState as we run the world for one additional frame
+                    //to collect the flow
+                    value = _.last(flowStates[flowStates.length - 1].statements).variables[prop];
+                }
+                catch (e)
+                {
+                    value = undefined;
+                }
+
+            }
+            else
+            {
+                value = value[prop];
+            }
+            keys.push(prop);
+            if (!value) break;
+            var classOfValue;
+            if (classOfValue = serializedClasses[value.CN])
+            {
+                if (value.CN === "Thang")
+                {
+                    var thang = self.debugWorld.thangMap[value.id];
+                    value = thang || "<Thang " + value.id + " (non-existent)>"
+                }
+                else
+                {
+                    value = classOfValue.deserializeFromAether(value);
+                }
+            }
+        }
+        var serializedProperty = {
+            "key": keys.join("."),
+            "value": self.stringifyValue(value,0)
+        };
+        self.updateCache(currentThangID,currentSpellID,variableChain, args.frame, serializedProperty.value);
+        self.postMessage({type: 'debug-value-return', serialized: serializedProperty});
+    };
+    self.enableFlowOnThangSpell(args.currentThangID, args.currentSpellID, args.userCodeMap);
+    self.setupDebugWorldToRunUntilFrame(args);
+    self.debugWorld.loadFrames(
+        retrieveProperty.bind({},args.currentThangID, args.currentSpellID, args.variableChain),
+        self.onDebugWorldError,
+        self.onDebugWorldProgress,
+        false,
+        args.frame
+    );
+};
+
+self.enableFlowOnThangSpell = function (thangID, spellID, userCodeMap) {
+    try {
+        if (userCodeMap[thangID][spellID].originalOptions.includeFlow === true &&
+            userCodeMap[thangID][spellID].originalOptions.noSerializationInFlow === true)
+            return;
+        else
+        {
+            userCodeMap[thangID][spellID].originalOptions.includeFlow = true;
+            userCodeMap[thangID][spellID].originalOptions.noSerializationInFlow = true;
+            var temporaryAether = Aether.deserialize(userCodeMap[thangID][spellID]);
+            temporaryAether.transpile(temporaryAether.raw);
+            userCodeMap[thangID][spellID] = temporaryAether.serialize();
+        }
+
+    }
+    catch (e) {
+        console.log("there was an error enabling flow on thang spell:" + e)
+    }
+};
+
+self.setupDebugWorldToRunUntilFrame = function (args) {
+    self.debugPostedErrors = {};
+    self.debugt0 = new Date();
+    self.debugPostedErrors = false;
+    self.logsLogged = 0;
+
+    var stringifiedUserCodeMap = JSON.stringify(args.userCodeMap);
+    var userCodeMapHasChanged = ! _.isEqual(self.currentUserCodeMapCopy, stringifiedUserCodeMap);
+    self.currentUserCodeMapCopy = stringifiedUserCodeMap;
+    if (args.frame != self.currentDebugWorldFrame) self.invalidateCache();
+    if (!self.debugWorld || userCodeMapHasChanged || args.frame < self.currentDebugWorldFrame) {
+        try {
+            self.debugWorld = new World(args.worldName, args.userCodeMap);
+            if (args.level)
+                self.debugWorld.loadFromLevel(args.level, true);
+            self.debugGoalManager = new GoalManager(self.debugWorld);
+            self.debugGoalManager.setGoals(args.goals);
+            self.debugGoalManager.setCode(args.userCodeMap);
+            self.debugGoalManager.worldGenerationWillBegin();
+            self.debugWorld.setGoalManager(self.debugGoalManager);
+        }
+        catch (error) {
+            self.onDebugWorldError(error);
+            return;
+        }
+        Math.random = self.debugWorld.rand.randf;  // so user code is predictable
+    }
+    self.debugWorld.totalFrames = args.frame; //hack to work around error checking
+    self.currentDebugWorldFrame = args.frame;
+};
+
+
+self.onDebugWorldLoaded = function onDebugWorldLoaded() {
+    console.log("World loaded!");
+};
+
+self.onDebugWorldError = function onDebugWorldError(error) {
+
+    if(!error.isUserCodeProblem) {
+        console.log("Debug Non-UserCodeError:", error.toString() + "\n" + error.stack || error.stackTrace);
+    }
+    return true;
+};
+
+self.onDebugWorldProgress = function onDebugWorldProgress(progress) {
+    self.postMessage({type: 'debug-world-load-progress-changed', progress: progress});
+};
+
+self.debugAbort = function () {
+    if(self.debugWorld && self.debugWorld.name) {
+        console.log("About to abort:", self.debugWorld.name, typeof self.debugWorld.abort);
+        if(typeof self.debugWorld !== "undefined")
+            self.debugWorld.abort();
+        self.debugWorld = null;
+    }
+    self.postMessage({type: 'debugAbort'});
+};
+
 self.runWorld = function runWorld(args) {
   self.postedErrors = {};
   self.t0 = new Date();
   self.firstWorld = args.firstWorld;
   self.postedErrors = false;
   self.logsLogged = 0;
-  
+
   try {
     self.world = new World(args.worldName, args.userCodeMap);
     if(args.level)
@@ -136,11 +400,11 @@ self.onWorldLoaded = function onWorldLoaded() {
 };
 
 self.onWorldError = function onWorldError(error) {
-  if(error instanceof Aether.problems.UserCodeProblem) {
-    if(!self.postedErrors[error.key]) {
-      var problem = error.serialize();
-      self.postMessage({type: 'user-code-problem', problem: problem});
-      self.postedErrors[error.key] = problem;
+  if(error.isUserCodeProblem) {
+    var errorKey = error.userInfo.key;
+    if(!errorKey || !self.postedErrors[errorKey]) {
+      self.postMessage({type: 'user-code-problem', problem: error});
+      self.postedErrors[errorKey] = error;
     }
   }
   else {
diff --git a/app/lib/Buddha.coffee b/app/lib/Buddha.coffee
new file mode 100644
index 000000000..25ac3e21e
--- /dev/null
+++ b/app/lib/Buddha.coffee
@@ -0,0 +1,241 @@
+#Sane rewrite of God (a thread pool)
+{now} = require 'lib/world/world_utils'
+World = require 'lib/world/world'
+
+###
+  Every Angel has exactly one WebWorker attached to it.
+  It will call methods inside the webwrker and kill it if it times out.
+###
+class Angel
+  @cyanide: 0xDEADBEEF
+
+  infiniteLoopIntervalDuration: 7500  # check this often (must be more than the others added)
+  infiniteLoopTimeoutDuration: 10000  # wait this long when we check
+  abortTimeoutDuration: 500  # give in-process or dying workers this long to give up
+
+  constructor: (@id, @shared) ->
+    console.log @id + ": Creating Angel"
+    if (navigator.userAgent or navigator.vendor or window.opera).search("MSIE") isnt -1
+      @infiniteLoopIntervalDuration *= 20  # since it's so slow to serialize without transferable objects, we can't trust it
+      @infiniteLoopTimeoutDuration *= 20
+      @abortTimeoutDuration *= 10
+    @initialized = false
+    @running = false
+    @hireWorker()
+    @shared.angels.push @
+
+  testWorker: =>
+    if @initialized
+      @worker.postMessage {func: 'reportIn'}
+      # Are there any errors when webworker isn't loaded properly?
+
+  onWorkerMessage: (event) =>
+    #console.log JSON.stringify event
+    if @aborting and not
+    event.data.type is 'abort'
+      console.log id + " is currently aborting old work."
+      return
+
+    switch event.data.type
+      when 'start-load-frames'
+        clearTimeout(@condemnTimeout)
+        @condemnTimeout = _.delay @infinitelyLooped, @infiniteLoopTimeoutDuration
+      when 'end-load-frames'
+        console.log @id + ': No condemn this time.'
+        clearTimeout(@condemnTimeout)
+      when 'worker-initialized'
+        unless @initialized
+          console.log @id + ": Worker initialized after", ((new Date()) - @worker.creationTime), "ms"
+          @initialized = true
+          @doWork()
+      when 'new-world'
+        @beholdWorld event.data.serialized, event.data.goalStates
+      when 'world-load-progress-changed'
+        Backbone.Mediator.publish 'god:world-load-progress-changed', event.data
+      when 'console-log'
+        console.log "|" + @id + "|", event.data.args...
+      when 'user-code-problem'
+        Backbone.Mediator.publish 'god:user-code-problem', problem: event.data.problem
+      when 'abort'
+        console.log @id, "aborted."
+        clearTimeout @abortTimeout
+        @aborting = false
+        @running = false
+        @shared.busyAngels.pop @
+        @doWork()
+      when 'reportIn'
+        clearTimeout @condemnTimeout
+      else
+        console.log @id + " received unsupported message:", event.data
+
+  beholdWorld: (serialized, goalStates) ->
+    return if @aborting
+    unless serialized
+      # We're only interested in goalStates. (Simulator)
+      @latestGoalStates = goalStates
+      Backbone.Mediator.publish('god:goals-calculated', goalStates: goalStates)
+      @running = false
+      @shared.busyAngels.pop @
+
+    # console.warn "Goal states: " + JSON.stringify(goalStates)
+
+    window.BOX2D_ENABLED = false  # Flip this off so that if we have box2d in the namespace, the Collides Components still don't try to create bodies for deserialized Thangs upon attachment
+    World.deserialize serialized, @shared.worldClassMap, @lastSerializedWorldFrames, @finishBeholdingWorld(goalStates)
+    window.BOX2D_ENABLED = true
+    @lastSerializedWorldFrames = serialized.frames
+
+  finishBeholdingWorld: (goalStates) => (world) =>
+    return if @aborting
+    world.findFirstChangedFrame @shared.world
+    @shared.world = world
+    errorCount = (t for t in @shared.world.thangs when t.errorsOut).length
+    Backbone.Mediator.publish('god:new-world-created', world: world, firstWorld: @shared.firstWorld, errorCount: errorCount, goalStates: goalStates)
+    for scriptNote in @shared.world.scriptNotes
+      Backbone.Mediator.publish scriptNote.channel, scriptNote.event
+    @shared.goalManager?.world = world
+    @running = false
+    @shared.busyAngels.pop @
+    @shared.firstWorld = false
+    @doWork()
+
+  infinitelyLooped: =>
+    unless @aborting
+      problem = type: "runtime", level: "error", id: "runtime_InfiniteLoop", message: "Code never finished. It's either really slow or has an infinite loop."
+      Backbone.Mediator.publish 'god:user-code-problem', problem: problem
+      Backbone.Mediator.publish 'god:infinite-loop', firstWorld: @shared.firstWorld
+      @fireWorker()
+
+  workIfIdle: ->
+    @doWork() unless @running
+
+  doWork: =>
+    #console.log "work."
+    return if @aborted
+    console.log @id + " ready and looking for work. WorkQueue length is " + @shared.workQueue.length
+    if @initialized and @shared.workQueue.length
+      work = @shared.workQueue.pop()
+      if work is Angel.cyanide # Kill all other Angels, too
+        console.log @id + ": 'work is poison'"
+        @shared.workQueue.push Angel.cyanide
+        @free()
+      else
+        console.log @id + ": Sending the worker to work."
+        @running = true
+        @shared.busyAngels.push @
+
+        console.log "Running world..."
+        #console.error "worker.postMessage: " + @worker.postMessage + ", work: " + work
+        @worker.postMessage func: 'runWorld', args: work
+        console.log @id + ": Setting interval."
+        clearTimeout @purgatoryTimer
+        @purgatoryTimer = setInterval @testWorker, @infiniteLoopIntervalDuration
+    else
+      console.log "No work for " + @id
+      @hireWorker()
+
+  abort: =>
+    if @worker and @running
+      console.log "Aborting " + @id
+      @running = false
+      @shared.busyAngels.pop @
+      @abortTimeout = _.delay @terminate, @fireWorker, @abortTimeoutDuration
+      @worker.postMessage func: 'abort'
+      @aborting = true
+      @work = null
+
+  fireWorker: (rehire=true) =>
+    @aborting = false
+    @running = false
+    @shared.busyAngels.pop @
+    @worker?.removeEventListener 'message', @onWorkerMessage
+    @worker?.terminate()
+    @worker = null
+    clearTimeout @condemnTimeout
+    clearInterval @purgatoryTimer
+    console.log "Fired worker."
+    @initialized = false
+    @work = null
+    @hireWorker() if rehire
+
+  hireWorker: ->
+    unless @worker
+      console.log @id + ": Hiring worker."
+      @worker = new Worker @shared.workerCode
+      @worker.addEventListener 'message', @onWorkerMessage
+      @worker.creationTime = new Date()
+    #@worker.postMessage func: 'initialized' else
+
+  kill: ->
+    @fireWorker false
+    @shared.angels.pop @
+    clearTimeout @condemnTimeout
+    clearTimeout @purgatoryTimer
+    @purgatoryTimer = null
+    @condemnTimeout = null
+
+module.exports = class God
+  ids: ['Athena', 'Baldr', 'Crom', 'Dagr', 'Eris', 'Freyja', 'Great Gish', 'Hades', 'Ishtar', 'Janus', 'Khronos', 'Loki', 'Marduk', 'Negafook', 'Odin', 'Poseidon', 'Quetzalcoatl', 'Ra', 'Shiva', 'Thor', 'Umvelinqangi', 'Týr', 'Vishnu', 'Wepwawet', 'Xipe Totec', 'Yahweh', 'Zeus', '上帝', 'Tiamat', '盘古', 'Phoebe', 'Artemis', 'Osiris', "嫦娥", 'Anhur', 'Teshub', 'Enlil', 'Perkele', 'Chaos', 'Hera', 'Iris', 'Theia', 'Uranus', 'Stribog', 'Sabazios', 'Izanagi', 'Ao', 'Tāwhirimātea', 'Tengri', 'Inmar', 'Torngarsuk', 'Centzonhuitznahua', 'Hunab Ku', 'Apollo', 'Helios', 'Thoth', 'Hyperion', 'Alectrona', 'Eos', 'Mitra', 'Saranyu', 'Freyr', 'Koyash', 'Atropos', 'Clotho', 'Lachesis', 'Tyche', 'Skuld', 'Urðr', 'Verðandi', 'Camaxtli', 'Huhetotl', 'Set', 'Anu', 'Allah', 'Anshar', 'Hermes', 'Lugh', 'Brigit', 'Manannan Mac Lir', 'Persephone', 'Mercury', 'Venus', 'Mars', 'Azrael', 'He-Man', 'Anansi', 'Issek', 'Mog', 'Kos', 'Amaterasu Omikami', 'Raijin', 'Susanowo', 'Blind Io', 'The Lady', 'Offler', 'Ptah', 'Anubis', 'Ereshkigal', 'Nergal', 'Thanatos', 'Macaria', 'Angelos', 'Erebus', 'Hecate', 'Hel', 'Orcus', 'Ishtar-Deela Nakh', 'Prometheus', 'Hephaestos', 'Sekhmet', 'Ares', 'Enyo', 'Otrera', 'Pele', 'Hadúr', 'Hachiman', 'Dayisun Tngri', 'Ullr', 'Lua', 'Minerva']
+  nextID: ->
+    @lastID = (if @lastID? then @lastID + 1 else Math.floor(@ids.length * Math.random())) % @ids.length
+    @ids[@lastID]
+
+  # Charlie's Angels are all given access to this.
+  angelsShare: {
+    workerCode: '/javascripts/workers/worker_world.js' # Either path or function
+    workQueue: []
+    firstWorld: true
+    world: undefined
+    goalManager: undefined
+    worldClassMap: undefined
+    angels: []
+    busyAngels: [] #  Busy angels will automatically register here.
+  }
+
+  constructor: (options) ->
+    options ?= {}
+
+    @angelsShare.workerCode = options.workerCode if options.workerCode
+
+    # ~20MB per idle worker + angel overhead - in this implementation, every Angel maps to 1 worker
+    angelCount = options.maxAngels ? options.maxWorkerPoolSize ? 2  # How many concurrent Angels/web workers to use at a time
+
+    _.delay (=>new Angel @nextID(), @angelsShare), 250 * i for i in [0...angelCount] # Don't generate all Angels at once.
+    Backbone.Mediator.subscribe 'tome:cast-spells', @onTomeCast, @
+
+  onTomeCast: (e) ->
+    @createWorld e.spells
+
+  setGoalManager: (goalManager) =>
+    @angelsShare.goalManager = goalManager
+
+  setWorldClassMap: (worldClassMap) =>
+    @angelsShare.worldClassMap = worldClassMap
+
+  getUserCodeMap: (spells) ->
+    userCodeMap = {}
+    for spellKey, spell of spells
+      for thangID, spellThang of spell.thangs
+        (userCodeMap[thangID] ?= {})[spell.name] = spellThang.aether.serialize()
+
+    #console.log userCodeMap
+    userCodeMap
+
+  createWorld: (spells) =>
+    angel.abort() for angel in @angelsShare.busyAngels # We really only ever want one world calculated per God
+    #console.log "Level: " + @level
+    @angelsShare.workQueue.push
+      worldName: @level.name
+      userCodeMap: @getUserCodeMap(spells)
+      level: @level
+      goals: @angelsShare.goalManager?.getGoals()
+    angel.workIfIdle() for angel in @angelsShare.angels
+
+  destroy: =>
+    console.log "Destroying Buddha"
+    @createWorld = -> console.log "CreateWorld already gone."
+    @angelsShare.workQueue.push Angel.cyanide
+    angel.kill for angel in @angelsShare.busyAngels
+    Backbone.Mediator.unsubscribe('tome:cast-spells', @onTomeCast, @)
+    @angelsShare.goalManager?.destroy()
+    @angelsShare.goalManager = null
+    @angelsShare = null
diff --git a/app/lib/God.coffee b/app/lib/God.coffee
index 6fe758391..583c95ab7 100644
--- a/app/lib/God.coffee
+++ b/app/lib/God.coffee
@@ -18,16 +18,23 @@ module.exports = class God
     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
+    @workerCode = options.workerCode if options.workerCode?
     @angels = []
     @firstWorld = true
     Backbone.Mediator.subscribe 'tome:cast-spells', @onTomeCast, @
+    @retriveValueFromFrame = _.throttle @retrieveValueFromFrame, 1000
+    Backbone.Mediator.subscribe 'tome:spell-debug-value-request', @retrieveValueFromFrame, @
     @fillWorkerPool = _.throttle @fillWorkerPool, 3000, leading: false
     @fillWorkerPool()
+    #TODO: have this as a constructor option
+    @debugWorker = @createDebugWorker()
+    @currentUserCodeMap = {}
+
+  workerCode: '/javascripts/workers/worker_world.js'  #Can be a string or a function.
 
   onTomeCast: (e) ->
     return if @dead
-    @spells = e.spells
-    @createWorld()
+    @createWorld e.spells
 
   fillWorkerPool: =>
     return unless Worker and not @dead
@@ -44,17 +51,40 @@ module.exports = class God
     @createWorker()
 
   createWorker: ->
-    worker = new Worker '/javascripts/workers/worker_world.js'
+    worker = new Worker @workerCode
     worker.creationTime = new Date()
-    worker.addEventListener 'message', @onWorkerMessage
+    worker.addEventListener 'message', @onWorkerMessage(worker)
     worker
 
-  onWorkerMessage: (event) =>
+  createDebugWorker: ->
+    worker = new Worker '/javascripts/workers/worker_world.js'
+    worker.creationTime = new Date()
+    worker.addEventListener 'message', @onDebugWorkerMessage
+    worker
+
+
+  onWorkerMessage: (worker) =>
+    unless worker.onMessage?
+      worker.onMessage = (event) =>
+        if event.data.type is 'worker-initialized'
+          console.log @id, "worker initialized after", ((new Date()) - worker.creationTime), "ms (before it was needed)"
+          worker.initialized = true
+          worker.removeEventListener 'message', worker.onMessage
+        else
+          console.warn "Received strange word from God: #{event.data.type}"
+    worker.onMessage
+
+  onDebugWorkerMessage: (event) =>
     worker = event.target
-    if event.data.type is 'worker-initialized'
-      #console.log @id, "worker initialized after", ((new Date()) - worker.creationTime), "ms (before it was needed)"
-      worker.initialized = true
-      worker.removeEventListener 'message', @onWorkerMessage
+    switch event.data.type
+      when "worker-initialized"
+        worker.initialized = true
+      when 'new-debug-world'
+        console.log "New Debug world!"
+      when 'console-log'
+        console.log "|" + @id + "'s debugger|", event.data.args...
+      when 'debug-value-return'
+        Backbone.Mediator.publish 'god:debug-value-return', event.data.serialized
 
   getAngel: ->
     freeAngel = null
@@ -83,10 +113,10 @@ module.exports = class God
 
   angelUserCodeProblem: (angel, problem) ->
     return if @dead
-    #console.log "UserCodeProblem:", '"' + problem.message + '"', "for", problem.userInfo.thangID, "-", problem.userInfo.methodName, 'at line', problem.ranges?[0][0][0], 'column', problem.ranges?[0][0][1]
+    #console.log "UserCodeProblem:", '"' + problem.message + '"', "for", problem.userInfo.thangID, "-", problem.userInfo.methodName, 'at', problem.range?[0]
     Backbone.Mediator.publish 'god:user-code-problem', problem: problem
 
-  createWorld: ->
+  createWorld: (@spells) ->
     #console.log @id + ': "Let there be light upon', @world.name + '!"'
     unless Worker?  # profiling world simulation is easier on main thread, or we are IE9
       setTimeout @simulateWorld, 1
@@ -101,26 +131,59 @@ module.exports = class God
     #console.log "going to run world with code", @getUserCodeMap()
     angel.worker.postMessage {func: 'runWorld', args: {
       worldName: @level.name
-      userCodeMap: @getUserCodeMap()
+      userCodeMap: @getUserCodeMap(spells)
       level: @level
       firstWorld: @firstWorld
       goals: @goalManager?.getGoals()
     }}
 
+  retrieveValueFromFrame: (args) ->
+    if not args.thangID or not args.spellID or not args.variableChain then return
+    args.frame ?= @world.age / @world.dt
+    @debugWorker.postMessage
+      func: 'retrieveValueFromFrame'
+      args:
+        worldName: @level.name
+        userCodeMap: @currentUserCodeMap
+        level: @level
+        goals: @goalManager?.getGoals()
+        frame: args.frame
+        currentThangID: args.thangID
+        currentSpellID: args.spellID
+        variableChain: args.variableChain
+
+  #Coffeescript needs getters and setters.
+  setGoalManager: (@goalManager) =>
+
+  setWorldClassMap: (@worldClassMap) =>
+
   beholdWorld: (angel, serialized, goalStates) ->
+    unless serialized
+      # We're only interested in goalStates.
+      @latestGoalStates = goalStates
+      Backbone.Mediator.publish('god:goals-calculated', goalStates: goalStates, team: me.team)
+      unless _.find @angels, 'busy'
+        @spells = null  # Don't hold onto old spells; memory leaks
+      return
+
+    console.log "Beholding world."
     worldCreation = angel.started
     angel.free()
     return if @latestWorldCreation? and worldCreation < @latestWorldCreation
     @latestWorldCreation = worldCreation
     @latestGoalStates = goalStates
+
+    console.warn "Goal states: " + JSON.stringify(goalStates)
+
     window.BOX2D_ENABLED = false  # Flip this off so that if we have box2d in the namespace, the Collides Components still don't try to create bodies for deserialized Thangs upon attachment
-    World.deserialize serialized, @worldClassMap, @lastSerializedWorldFrames, worldCreation, @finishBeholdingWorld
+    World.deserialize serialized, @worldClassMap, @lastSerializedWorldFrames, @finishBeholdingWorld
     window.BOX2D_ENABLED = true
     @lastSerializedWorldFrames = serialized.frames
 
   finishBeholdingWorld: (newWorld) =>
     newWorld.findFirstChangedFrame @world
     @world = newWorld
+    @currentUserCodeMap = @filterUserCodeMapWhenFromWorld @world.userCodeMap
     errorCount = (t for t in @world.thangs when t.errorsOut).length
     Backbone.Mediator.publish('god:new-world-created', world: @world, firstWorld: @firstWorld, errorCount: errorCount, goalStates: @latestGoalStates, team: me.team)
     for scriptNote in @world.scriptNotes
@@ -131,6 +194,23 @@ module.exports = class God
     unless _.find @angels, 'busy'
       @spells = null  # Don't hold onto old spells; memory leaks
 
+  filterUserCodeMapWhenFromWorld: (worldUserCodeMap) ->
+    newUserCodeMap = {}
+    for thangName, thang of worldUserCodeMap
+      newUserCodeMap[thangName] = {}
+      for spellName,aether of thang
+        shallowFilteredObject = _.pick aether, ['raw','pure','originalOptions']
+        newUserCodeMap[thangName][spellName] = _.cloneDeep shallowFilteredObject
+        newUserCodeMap[thangName][spellName] = _.defaults newUserCodeMap[thangName][spellName],
+          flow: {}
+          metrics: {}
+          problems:
+            errors: []
+            infos: []
+            warnings: []
+          style: {}
+    newUserCodeMap
+
   getUserCodeMap: ->
     userCodeMap = {}
     for spellKey, spell of @spells
@@ -144,6 +224,10 @@ module.exports = class God
     @dead = true
     Backbone.Mediator.unsubscribe('tome:cast-spells', @onTomeCast, @)
     @goalManager?.destroy()
+    @debugWorker?.terminate()
+    @debugWorker?.removeEventListener 'message', @onDebugWorkerMessage
+    @debugWorker ?= null
+    @currentUserCodeMap = null
     @goalManager = null
     @fillWorkerPool = null
     @simulateWorld = null
@@ -171,7 +255,7 @@ module.exports = class God
     @latestGoalStates = @testGM?.getGoalStates()
     serialized = @testWorld.serialize().serializedWorld
     window.BOX2D_ENABLED = false
-    World.deserialize serialized, @worldClassMap, @lastSerializedWorldFrames, @t0, @finishBeholdingWorld
+    World.deserialize serialized, @worldClassMap, @lastSerializedWorldFrames, @finishBeholdingWorld
     window.BOX2D_ENABLED = true
     @lastSerializedWorldFrames = serialized.frames
 
@@ -255,7 +339,7 @@ class Angel
 
   testWorker: =>
     unless @worker.initialized
-      console.warn "Worker", @id, "hadn't even loaded the scripts yet after", @infiniteLoopIntervalDuration, "ms."
+      console.warn "Worker", @id, " hadn't even loaded the scripts yet after", @infiniteLoopIntervalDuration, "ms."
       return
     @worker.postMessage {func: 'reportIn'}
     @condemnTimeout = _.delay @condemnWorker, @infiniteLoopTimeoutDuration
@@ -271,6 +355,7 @@ class Angel
     switch event.data.type
       when 'worker-initialized'
         console.log "Worker", @id, "initialized after", ((new Date()) - @worker.creationTime), "ms (we had been waiting for it)"
+        @worker.initialized = true
       when 'new-world'
         @god.beholdWorld @, event.data.serialized, event.data.goalStates
       when 'world-load-progress-changed'
diff --git a/app/lib/LevelLoader.coffee b/app/lib/LevelLoader.coffee
index a276d0364..90a0675da 100644
--- a/app/lib/LevelLoader.coffee
+++ b/app/lib/LevelLoader.coffee
@@ -59,13 +59,13 @@ module.exports = class LevelLoader extends CocoClass
       url = "/db/level/#{@levelID}/session"
       url += "?team=#{@team}" if @team
 
-    @session = new LevelSession().setURL url
-    @supermodel.loadModel(@session, 'level_session', {cache:false})
+    session = new LevelSession().setURL url
+    @session = @supermodel.loadModel(session, 'level_session', {cache:false}).model
     @session.once 'sync', -> @url = -> '/db/level.session/' + @id
 
     if @opponentSessionID
-      @opponentSession = new LevelSession().setURL "/db/level_session/#{@opponentSessionID}"
-      @supermodel.loadModel(@opponentSession, 'opponent_session')
+      opponentSession = new LevelSession().setURL "/db/level_session/#{@opponentSessionID}"
+      @opponentSession = @supermodel.loadModel(opponentSession, 'opponent_session').model
 
   # Supermodel (Level) Loading
 
@@ -155,6 +155,7 @@ module.exports = class LevelLoader extends CocoClass
   # Building sprite sheets
 
   buildSpriteSheetsForThangType: (thangType) ->
+    return if @headless
     @grabThangTypeTeams() unless @thangTypeTeams
     for team in @thangTypeTeams[thangType.get('original')] ? [null]
       spriteOptions = {resolutionFactor: 4, async: false}
diff --git a/app/lib/auth.coffee b/app/lib/auth.coffee
index 53cef7ced..e4fea6113 100644
--- a/app/lib/auth.coffee
+++ b/app/lib/auth.coffee
@@ -16,7 +16,7 @@ init = ->
 module.exports.createUser = (userObject, failure=backboneFailure, nextURL=null) ->
   user = new User(userObject)
   user.save({}, {
-    error: (model,jqxhr,options) -> 
+    error: (model,jqxhr,options) ->
       error = parseServerError(jqxhr.responseText)
       property = error.property if error.property
       if jqxhr.status is 409 and property is 'name'
@@ -26,12 +26,12 @@ module.exports.createUser = (userObject, failure=backboneFailure, nextURL=null)
         genericFailure(jqxhr)
     success: -> if nextURL then window.location.href = nextURL else window.location.reload()
   })
-  
+
 module.exports.createUserWithoutReload = (userObject, failure=backboneFailure) ->
   user = new User(userObject)
   user.save({}, {
     error: failure
-    success: -> 
+    success: ->
       Backbone.Mediator.publish("created-user-without-reload")
   })
 
diff --git a/app/lib/simulator/Simulator.coffee b/app/lib/simulator/Simulator.coffee
index 067317512..f1c41d38c 100644
--- a/app/lib/simulator/Simulator.coffee
+++ b/app/lib/simulator/Simulator.coffee
@@ -2,15 +2,21 @@ SuperModel = require 'models/SuperModel'
 CocoClass = require 'lib/CocoClass'
 LevelLoader = require 'lib/LevelLoader'
 GoalManager = require 'lib/world/GoalManager'
-God = require 'lib/God'
+God = require 'lib/Buddha'
+
+Aether.addGlobal 'Vector', require 'lib/world/vector'
+Aether.addGlobal '_', _
 
 module.exports = class Simulator extends CocoClass
 
-  constructor: ->
+  constructor: (@options) ->
+    @options ?= {}
     _.extend @, Backbone.Events
     @trigger 'statusUpdate', 'Starting simulation!'
     @retryDelayInSeconds = 10
     @taskURL = '/queue/scoring'
+    @simulatedByYou = 0
+    @god = new God maxWorkerPoolSize: 1, maxAngels: 1, workerCode: @options.workerCode  # Start loading worker.
 
   destroy: ->
     @off()
@@ -19,6 +25,17 @@ module.exports = class Simulator extends CocoClass
 
   fetchAndSimulateTask: =>
     return if @destroyed
+
+    if @options.headlessClient
+      if @dumpThisTime # The first heapdump would be useless to find leaks.
+        console.log "Writing snapshot."
+        @options.heapdump.writeSnapshot()
+      @dumpThisTime = true if @options.heapdump
+
+      if @options.testing
+        _.delay @setupSimulationAndLoadLevel, 0, @options.testFile, "Testing...", status: 400
+        return
+
     @trigger 'statusUpdate', 'Fetching simulation data!'
     $.ajax
       url: @taskURL
@@ -32,7 +49,9 @@ module.exports = class Simulator extends CocoClass
     @simulateAnotherTaskAfterDelay()
 
   handleNoGamesResponse: ->
-    @trigger 'statusUpdate', 'There were no games to simulate--all simulations are done or in process. Retrying in 10 seconds.'
+    info = 'There were no games to simulate--all simulations are done or in process. Retrying in 10 seconds.'
+    console.log info
+    @trigger 'statusUpdate', info
     @simulateAnotherTaskAfterDelay()
 
   simulateAnotherTaskAfterDelay: =>
@@ -53,7 +72,6 @@ module.exports = class Simulator extends CocoClass
       return
 
     @supermodel ?= new SuperModel()
-    @god = new God maxWorkerPoolSize: 1, maxAngels: 1  # Start loading worker.
 
     @levelLoader = new LevelLoader supermodel: @supermodel, levelID: levelID, sessionID: @task.getFirstSessionID(), headless: true
     if @supermodel.finished()
@@ -63,7 +81,9 @@ module.exports = class Simulator extends CocoClass
 
   simulateGame: ->
     return if @destroyed
-    @trigger 'statusUpdate', 'All resources loaded, simulating!', @task.getSessions()
+    info = 'All resources loaded, simulating!'
+    console.log info
+    @trigger 'statusUpdate', info, @task.getSessions()
     @assignWorldAndLevelFromLevelLoaderAndDestroyIt()
     @setupGod()
 
@@ -74,6 +94,7 @@ module.exports = class Simulator extends CocoClass
       @simulateAnotherTaskAfterDelay()
 
   assignWorldAndLevelFromLevelLoaderAndDestroyIt: ->
+    console.log "Assigning world and level"
     @world = @levelLoader.world
     @level = @levelLoader.level
     @levelLoader.destroy()
@@ -81,18 +102,45 @@ module.exports = class Simulator extends CocoClass
 
   setupGod: ->
     @god.level = @level.serialize @supermodel
-    @god.worldClassMap = @world.classMap
+    @god.setWorldClassMap @world.classMap
     @setupGoalManager()
     @setupGodSpells()
 
+
   setupGoalManager: ->
-    @god.goalManager = new GoalManager @world, @level.get 'goals'
+    @god.setGoalManager new GoalManager(@world, @level.get 'goals')
+
 
   commenceSimulationAndSetupCallback: ->
-    @god.createWorld()
+    @god.createWorld @generateSpellsObject()
     Backbone.Mediator.subscribeOnce 'god:infinite-loop', @onInfiniteLoop, @
     Backbone.Mediator.subscribeOnce 'god:new-world-created', @processResults, @
 
+    #Search for leaks, headless-client only.
+    if @options.headlessClient and @options.leakTest and not @memwatch?
+      leakcount = 0
+      maxleakcount = 0
+      console.log "Setting leak callbacks."
+      @memwatch = require 'memwatch'
+
+      @memwatch.on 'leak', (info) =>
+        console.warn "LEAK!!\n" + JSON.stringify(info)
+
+        unless @hd?
+          if (leakcount++ is maxleakcount)
+            @hd = new @memwatch.HeapDiff()
+
+            @memwatch.on 'stats', (stats) =>
+              console.warn "stats callback: " + stats
+              diff = @hd.end()
+              console.warn "HeapDiff:\n" + JSON.stringify(diff)
+
+              if @options.exitOnLeak
+                console.warn "Exiting because of Leak."
+                process.exit()
+              @hd = new @memwatch.HeapDiff()
+
+
   onInfiniteLoop: ->
     console.warn "Skipping infinitely looping game."
     @trigger 'statusUpdate', "Infinite loop detected; grabbing a new game in #{@retryDelayInSeconds} seconds."
@@ -106,6 +154,9 @@ module.exports = class Simulator extends CocoClass
     @trigger 'statusUpdate', 'Simulation completed, sending results back to server!'
     console.log "Sending result back to server!"
 
+    if @options.headlessClient and @options.testing
+      return @fetchAndSimulateTask()
+
     $.ajax
       url: "/queue/scoring"
       data: results
@@ -117,8 +168,11 @@ module.exports = class Simulator extends CocoClass
   handleTaskResultsTransferSuccess: (result) =>
     console.log "Task registration result: #{JSON.stringify result}"
     @trigger 'statusUpdate', 'Results were successfully sent back to server!'
-    simulatedBy = parseInt($('#simulated-by-you').text(), 10) + 1
-    $('#simulated-by-you').text(simulatedBy)
+    console.log "Simulated by you: " + @simulatedByYou
+    @simulatedByYou++
+    unless @options.headlessClient
+      simulatedBy = parseInt($('#simulated-by-you').text(), 10) + 1
+      $('#simulated-by-you').text(simulatedBy)
 
   handleTaskResultsTransferError: (error) =>
     @trigger 'statusUpdate', 'There was an error sending the results back to the server.'
@@ -144,7 +198,6 @@ module.exports = class Simulator extends CocoClass
       sessions: []
 
     for session in @task.getSessions()
-
       sessionResult =
         sessionID: session.sessionID
         submitDate: session.submitDate
@@ -242,8 +295,8 @@ module.exports = class Simulator extends CocoClass
       functionName: methodName
       protectAPI: useProtectAPI
       includeFlow: false
-      requiresThis: true
       yieldConditionally: false
+      globals: ['Vector', '_']
       problems:
         jshint_W040: {level: "ignore"}
         jshint_W030: {level: "ignore"}  # aether_NoEffect instead
diff --git a/app/lib/sprites/SpriteParser.coffee b/app/lib/sprites/SpriteParser.coffee
index 3138bb47b..0de48c013 100644
--- a/app/lib/sprites/SpriteParser.coffee
+++ b/app/lib/sprites/SpriteParser.coffee
@@ -379,7 +379,9 @@ module.exports = class SpriteParser
         argsSource = argsSource.replace(/cjs(.+)\)/, '"createjs$1)"') # turns cjs.Ease.get(0.5)
 
         args = eval "[#{argsSource}]"
-        if args[0]?.state?[0]?.t?.search?("shape") is 0 and not _.find(localShapes, bn: args[0].state[0].t)
+        shadowTween = args[0]?.search?('shape') is 0 and not _.find(localShapes, bn: args[0])
+        shadowTween = shadowTween or args[0]?.state?[0]?.t?.search?("shape") is 0 and not _.find(localShapes, bn: args[0].state[0].t)
+        if shadowTween
           console.log "Skipping tween", name, argsSource, args, "from localShapes", localShapes, "presumably because it's a shadow we skipped."
           return
         callExpressions.push {n: name, a: args}
diff --git a/app/lib/surface/CocoSprite.coffee b/app/lib/surface/CocoSprite.coffee
index 55a1982e4..225c6b308 100644
--- a/app/lib/surface/CocoSprite.coffee
+++ b/app/lib/surface/CocoSprite.coffee
@@ -168,10 +168,12 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
   stop: ->
     @imageObject?.stop?()
     mark.stop() for name, mark of @marks
+    @stopped = true
 
   play: ->
     @imageObject?.play?()
     mark.play() for name, mark of @marks
+    @stopped = false
 
   update: (frameChanged) ->
     # Gets the sprite to reflect what the current state of the thangs and surface are
@@ -222,7 +224,8 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
 
   getBobOffset: ->
     return 0 unless @thang.bobHeight
-    @thang.bobHeight * (1 + Math.sin(@age * Math.PI / @thang.bobTime))
+    return @lastBobOffset if @stopped
+    return @lastBobOffset = @thang.bobHeight * (1 + Math.sin(@age * Math.PI / @thang.bobTime))
 
   getWorldPosition: ->
     p1 = if @possessed then @shadow.pos else @thang.pos
@@ -495,6 +498,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
 
   updateEffectMarks: ->
     return if _.isEqual @thang.effectNames, @previousEffectNames
+    return if @stopped
     for effect in @thang.effectNames
       mark = @addMark effect, @options.floatingLayer, effect
       mark.statusEffect = true
diff --git a/app/lib/surface/Mark.coffee b/app/lib/surface/Mark.coffee
index 3d01a2ee5..7c9abb91f 100644
--- a/app/lib/surface/Mark.coffee
+++ b/app/lib/surface/Mark.coffee
@@ -246,6 +246,7 @@ module.exports = class Mark extends CocoClass
       size = @sprite.getAverageDimension()
       size += 60 if @name is 'selection'
       size += 60 if @name is 'repair'
+      size *= @sprite.scaleFactor
       scale = size / {selection: 128, target: 128, repair: 320, highlight: 160}[@name]
       if @sprite?.thang.spriteName.search(/(dungeon|indoor).wall/i) isnt -1
         scale *= 2
diff --git a/app/lib/surface/SpriteBoss.coffee b/app/lib/surface/SpriteBoss.coffee
index 03e05e0c3..1c7916817 100644
--- a/app/lib/surface/SpriteBoss.coffee
+++ b/app/lib/surface/SpriteBoss.coffee
@@ -221,12 +221,12 @@ module.exports = class SpriteBoss extends CocoClass
   onCastSpells: -> @stop()
 
   play: ->
-    sprite.imageObject.play() for sprite in @spriteArray
+    sprite.play() for sprite in @spriteArray
     @selectionMark?.play()
     @targetMark?.play()
 
   stop: ->
-    sprite.imageObject.stop() for sprite in @spriteArray
+    sprite.stop() for sprite in @spriteArray
     @selectionMark?.stop()
     @targetMark?.stop()
 
diff --git a/app/lib/surface/Surface.coffee b/app/lib/surface/Surface.coffee
index 49896f360..04b54a7e4 100644
--- a/app/lib/surface/Surface.coffee
+++ b/app/lib/surface/Surface.coffee
@@ -65,6 +65,7 @@ module.exports = Surface = class Surface extends CocoClass
     'god:new-world-created': 'onNewWorld'
     'tome:cast-spells': 'onCastSpells'
     'level-set-letterbox': 'onSetLetterbox'
+    'application:idle-changed': 'onIdleChanged'
 
   shortcuts:
     'ctrl+\\, ⌘+\\': 'onToggleDebug'
@@ -304,15 +305,32 @@ module.exports = Surface = class Surface extends CocoClass
       @spriteBoss.stop()
       @playbackOverScreen.show()
       @ended = true
+      @setPaused true
       Backbone.Mediator.publish 'surface:playback-ended'
     else if @currentFrame < @world.totalFrames and @ended
       @spriteBoss.play()
       @playbackOverScreen.hide()
       @ended = false
+      @setPaused false
       Backbone.Mediator.publish 'surface:playback-restarted'
 
     @lastFrame = @currentFrame
 
+  onIdleChanged: (e) ->
+    @setPaused e.idle unless @ended
+
+  setPaused: (to) ->
+    # We want to be able to essentially stop rendering the surface if it doesn't need to animate anything.
+    # If pausing, though, we want to give it enough time to finish any tweens.
+    performToggle = =>
+      createjs.Ticker.setFPS if to then 1 else @options.frameRate
+      @surfacePauseInterval = null
+    clearTimeout @surfacePauseInterval if @surfacePauseInterval
+    if to
+      @surfacePauseInterval = _.delay performToggle, 2000
+    else
+      performToggle()
+
   onCastSpells: ->
     @casting = true
     @wasPlayingWhenCastingBegan = @playing
@@ -575,8 +593,6 @@ module.exports = Surface = class Surface extends CocoClass
     @paths.parent.removeChild @paths
     @paths = null
 
-  # Screenshot
-
   screenshot: (scale=0.25, format='image/jpeg', quality=0.8, zoom=2) ->
     # Quality doesn't work with image/png, just image/jpeg and image/webp
     [w, h] = [@camera.canvasWidth, @camera.canvasHeight]
@@ -586,6 +602,5 @@ module.exports = Surface = class Surface extends CocoClass
     #console.log "Screenshot with scale", scale, "format", format, "quality", quality, "was", Math.floor(imageData.length / 1024), "kB"
     screenshot = document.createElement("img")
     screenshot.src = imageData
-    #$('body').append(screenshot)
     @stage.uncache()
     imageData
diff --git a/app/lib/world/GoalManager.coffee b/app/lib/world/GoalManager.coffee
index 073e87b05..324297db9 100644
--- a/app/lib/world/GoalManager.coffee
+++ b/app/lib/world/GoalManager.coffee
@@ -204,7 +204,7 @@ module.exports = class GoalManager extends CocoClass
 
     arrays = (prop for prop in whos when prop?.length)
     return unless arrays.length
-    state[progressObjectName] = {}
+    state[progressObjectName] ?= {}
     for array in arrays
       for thang in array
         if @thangTeams[thang]?
@@ -235,7 +235,7 @@ module.exports = class GoalManager extends CocoClass
       numNeeded = goal.howMany ? Math.max(1, _.size stateThangs)
     else
       # saveThangs: by default we would want to save all the Thangs, which means that we would want none of them to be "done"
-      numNeeded = _.size(stateThangs) - Math.min((goal.howMany ? 1), _.size stateThangs) + 1
+      numNeeded = _.size(stateThangs) - Math.max((goal.howMany ? 1), _.size stateThangs) + 1
     numDone = _.filter(stateThangs).length
     console.log "needed", numNeeded, "done", numDone, "of total", _.size(stateThangs), "with how many", goal.howMany, "and stateThangs", stateThangs
     return unless numDone >= numNeeded
diff --git a/app/lib/world/names.coffee b/app/lib/world/names.coffee
index d52471998..0e85c0d82 100644
--- a/app/lib/world/names.coffee
+++ b/app/lib/world/names.coffee
@@ -29,8 +29,6 @@ module.exports.thangNames = thangNames =
     "Phineas"
     "Ferb"
     "Felix"
-    "Arthur"
-    "Galahad"
     "Ezra"
     "Lucian"
     "Augustus"
@@ -174,6 +172,7 @@ module.exports.thangNames = thangNames =
     "Snortt"
     "Kog"
     "Ursa"
+    "Ragtime"
   ]
   "Ogre Munchkin F": [
     "Iyert"
@@ -184,6 +183,7 @@ module.exports.thangNames = thangNames =
     "Dosha"
     "Inski"
     "Lacos"
+    "Upfish"
   ]
   "Ogre M": [
     "Krogg"
@@ -202,6 +202,7 @@ module.exports.thangNames = thangNames =
     "Mokrul"
     "Polifemo"
     "Muthyala"
+    "Saltporker"
   ]
   "Ogre F": [
     "Nareng"
@@ -231,6 +232,8 @@ module.exports.thangNames = thangNames =
     "Tuguro"
     "York"
     "Ork'han"
+    "Roast Beefy"
+    "Haggar"
   ]
   "Ogre Fangrider": [
     "Dreek"
@@ -272,6 +275,7 @@ module.exports.thangNames = thangNames =
     "Gogg"
     "Ghuk"
     "Makas"
+    "Drun"
   ]
   "Ogre Thrower": [
     "Kyrgg"
@@ -293,10 +297,12 @@ module.exports.thangNames = thangNames =
   "Burl": [
     "Borlit"
     "Burlosh"
+    "Dorf"
   ]
   "Griffin Rider": [
     "Aeoldan"
     "Bestarius"
+
   ]
   "Potion Master": [
     "Snake"
@@ -305,6 +311,7 @@ module.exports.thangNames = thangNames =
     "Arora"
     "Curie"
     "Clause"
+    "Vanders"
   ]
   "Librarian": [
     "Hushbaum"
@@ -318,4 +325,30 @@ module.exports.thangNames = thangNames =
     "Ryder"
     "Thoron"
     "Mirial"
+    "Neely"
+  ]
+  "Knight": [
+    "Tharin"
+    "Arthur"
+    "Galahad"
+    "Mace"
+    "Drake"
+    "Duran"
+    "Almeric"
+    "Hunfray"
+    "Hank"
+    "Jeph"
+    "Neville"
+  ]
+  "Captain": [
+    "Anya"
+    "Brigette"
+    "Sarre"
+    "Katana"
+    "Lily"
+    "Isa"
+    "Dimia"
+    "Jane"
+    "Lia"
+    "Hardcastle"
   ]
diff --git a/app/lib/world/systems/collision.coffee b/app/lib/world/systems/collision.coffee
index a928e69e5..efceab106 100644
--- a/app/lib/world/systems/collision.coffee
+++ b/app/lib/world/systems/collision.coffee
@@ -32,7 +32,7 @@ module.exports.CollisionCategory = class CollisionCategory
     # "ground_and_air_<team>" units don't hit ground or air units on their team (so missiles don't hit same team)
     sameTeam = @superteamIndex and cat.superteamIndex is @superteamIndex
     return false if sameTeam and @ground and @air
-    
+
     # actually, "ground_and_air<team>" units don't hit any ground_and_air units (temp missile collision fix)
     return false if @ground and @air and cat.ground and cat.air
 
diff --git a/app/lib/world/world.coffee b/app/lib/world/world.coffee
index f209f82d3..3da7ae6df 100644
--- a/app/lib/world/world.coffee
+++ b/app/lib/world/world.coffee
@@ -8,7 +8,6 @@ WorldScriptNote = require './world_script_note'
 {now, consolidateThangs, typedArraySupport} = require './world_utils'
 Component = require 'lib/world/component'
 System = require 'lib/world/system'
-
 PROGRESS_UPDATE_INTERVAL = 200
 DESERIALIZATION_INTERVAL = 20
 
@@ -72,14 +71,18 @@ module.exports = class World
     (@runtimeErrors ?= []).push error
     (@unhandledRuntimeErrors ?= []).push error
 
-  loadFrames: (loadedCallback, errorCallback, loadProgressCallback) ->
+  loadFrames: (loadedCallback, errorCallback, loadProgressCallback, skipDeferredLoading, loadUntilFrame) ->
     return if @aborted
     unless @thangs.length
       console.log "Warning: loadFrames called on empty World (no thangs)."
     t1 = now()
     @t0 ?= t1
+    if loadUntilFrame
+      frameToLoadUntil = loadUntilFrame + 1
+    else
+      frameToLoadUntil = @totalFrames
     i = @frames.length
-    while i < @totalFrames
+    while i < frameToLoadUntil
       try
         @getFrame(i)
         ++i  # increment this after we have succeeded in getting the frame, otherwise we'll have to do that frame again
@@ -96,10 +99,19 @@ module.exports = class World
         if t2 - @t0 > 1000
           console.log('  Loaded', i, 'of', @totalFrames, "(+" + (t2 - @t0).toFixed(0) + "ms)")
           @t0 = t2
-        setTimeout((=> @loadFrames(loadedCallback, errorCallback, loadProgressCallback)), 0)
+        continueFn = => 
+          if loadUntilFrame
+            @loadFrames(loadedCallback,errorCallback,loadProgressCallback, skipDeferredLoading, loadUntilFrame)
+          else
+            @loadFrames(loadedCallback, errorCallback, loadProgressCallback, skipDeferredLoading)
+        if skipDeferredLoading
+          continueFn()
+        else
+          setTimeout(continueFn, 0)
         return
-    @ended = true
-    system.finish @thangs for system in @systems
+    unless loadUntilFrame
+      @ended = true
+      system.finish @thangs for system in @systems
     loadProgressCallback? 1
     loadedCallback()
 
@@ -221,7 +233,7 @@ module.exports = class World
       @scriptNotes.push scriptNote
     return unless @goalManager
     @goalManager.submitWorldGenerationEvent(channel, event, @frames.length)
-    
+
   setGoalState: (goalID, status) ->
     @goalManager.setGoalState(goalID, status)
 
@@ -336,7 +348,7 @@ module.exports = class World
       console.log "Whoa, serializing a lot of WorldScriptNotes here:", o.scriptNotes.length
     {serializedWorld: o, transferableObjects: [o.storageBuffer]}
 
-  @deserialize: (o, classMap, oldSerializedWorldFrames, worldCreationTime, finishedWorldCallback) ->
+  @deserialize: (o, classMap, oldSerializedWorldFrames, finishedWorldCallback) ->
     # Code hotspot; optimize it
     #console.log "Deserializing", o, "length", JSON.stringify(o).length
     #console.log JSON.stringify(o)
diff --git a/app/locale/ar.coffee b/app/locale/ar.coffee
index 42b9b617e..60847fffd 100644
--- a/app/locale/ar.coffee
+++ b/app/locale/ar.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "العربية", englishDescription: "Arabi
 #    multiplayer_hint_label: "Hint:"
 #    multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
 #    multiplayer_coming_soon: "More multiplayer features to come!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
 #    guide_title: "Guide"
 #    tome_minion_spells: "Your Minions' Spells"
 #    tome_read_only_spells: "Read-Only Spells"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "العربية", englishDescription: "Arabi
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/bg.coffee b/app/locale/bg.coffee
index 32f930ab4..712b83d4c 100644
--- a/app/locale/bg.coffee
+++ b/app/locale/bg.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "български език", englishDescri
 #    multiplayer_hint_label: "Hint:"
 #    multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
 #    multiplayer_coming_soon: "More multiplayer features to come!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
 #    guide_title: "Guide"
 #    tome_minion_spells: "Your Minions' Spells"
 #    tome_read_only_spells: "Read-Only Spells"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "български език", englishDescri
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/ca.coffee b/app/locale/ca.coffee
index 763a73766..a609dd265 100644
--- a/app/locale/ca.coffee
+++ b/app/locale/ca.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
 #    multiplayer_hint_label: "Hint:"
 #    multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
 #    multiplayer_coming_soon: "More multiplayer features to come!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
 #    guide_title: "Guide"
 #    tome_minion_spells: "Your Minions' Spells"
 #    tome_read_only_spells: "Read-Only Spells"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/cs.coffee b/app/locale/cs.coffee
index 0568b5128..c06ba4be2 100644
--- a/app/locale/cs.coffee
+++ b/app/locale/cs.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "čeština", englishDescription: "Czech", tr
     multiplayer_hint_label: "Tip:"
     multiplayer_hint: " Klikněte na odkaz pro jeho výběr, poté stiskněte ⌘-C nebo Ctrl-C pro kopírování odkazu."
     multiplayer_coming_soon: "Další vlastnosti multiplayeru jsou na cestě!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Průvodce"
     tome_minion_spells: "Vaše oblíbená kouzla"
     tome_read_only_spells: "Kouzla jen pro čtení"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "čeština", englishDescription: "Czech", tr
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/da.coffee b/app/locale/da.coffee
index 01c6f1b79..b7f579fb8 100644
--- a/app/locale/da.coffee
+++ b/app/locale/da.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "dansk", englishDescription: "Danish", trans
     multiplayer_hint_label: "Tip:"
     multiplayer_hint: " Klik på linket for markere alt; tryk derefter ⌘-C eller Ctrl-C tfr at kopiere linket."
     multiplayer_coming_soon: "Yderligere flerspillermuligheder er på vej!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Instruktioner"
 #    tome_minion_spells: "Your Minions' Spells"
 #    tome_read_only_spells: "Read-Only Spells"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "dansk", englishDescription: "Danish", trans
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/de-AT.coffee b/app/locale/de-AT.coffee
index 80df6de2f..b1bca6e27 100644
--- a/app/locale/de-AT.coffee
+++ b/app/locale/de-AT.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "Deutsch (Österreich)", englishDescription:
 #    multiplayer_hint_label: "Hint:"
 #    multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
 #    multiplayer_coming_soon: "More multiplayer features to come!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
 #    guide_title: "Guide"
 #    tome_minion_spells: "Your Minions' Spells"
 #    tome_read_only_spells: "Read-Only Spells"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "Deutsch (Österreich)", englishDescription:
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/de-CH.coffee b/app/locale/de-CH.coffee
index 6203a96a9..46694a416 100644
--- a/app/locale/de-CH.coffee
+++ b/app/locale/de-CH.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "Deutsch (Schweiz)", englishDescription: "Ge
 #    multiplayer_hint_label: "Hint:"
 #    multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
 #    multiplayer_coming_soon: "More multiplayer features to come!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
 #    guide_title: "Guide"
 #    tome_minion_spells: "Your Minions' Spells"
 #    tome_read_only_spells: "Read-Only Spells"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "Deutsch (Schweiz)", englishDescription: "Ge
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/de-DE.coffee b/app/locale/de-DE.coffee
index 91a82ba98..51faa5412 100644
--- a/app/locale/de-DE.coffee
+++ b/app/locale/de-DE.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
     multiplayer_hint_label: "Hinweis:"
     multiplayer_hint: " Klick den Link, um alles auszuwählen, dann drück ⌘-C oder Strg-C um den Link zu kopieren."
     multiplayer_coming_soon: "Mehr Multiplayerfeatures werden kommen!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Anleitung"
     tome_minion_spells: "Die Zaubersprüche Deiner Knechte"
     tome_read_only_spells: "Nur-lesen Zauberspüche"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/de.coffee b/app/locale/de.coffee
index 5a6ad4372..8874f2477 100644
--- a/app/locale/de.coffee
+++ b/app/locale/de.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "Deutsch", englishDescription: "German", tra
     multiplayer_hint_label: "Hinweis:"
     multiplayer_hint: " Klick den Link, um alles auszuwählen, dann drück ⌘-C oder Strg-C um den Link zu kopieren."
     multiplayer_coming_soon: "Mehr Multiplayerfeatures werden kommen!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Anleitung"
     tome_minion_spells: "Die Zaubersprüche Deiner Knechte"
     tome_read_only_spells: "Nur-lesen Zauberspüche"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "Deutsch", englishDescription: "German", tra
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/el.coffee b/app/locale/el.coffee
index 7ccc8b35c..86645355d 100644
--- a/app/locale/el.coffee
+++ b/app/locale/el.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "ελληνικά", englishDescription: "Gre
     multiplayer_hint_label: "Συμβουλή:"
     multiplayer_hint: " Κάντε κλικ στο σύνδεσμο για να επιλέξετε όλα, στη συνέχεια, πατήστε την Apple-C ή Ctrl-C για να αντιγράψετε το σύνδεσμο."
     multiplayer_coming_soon: "Περισσότερα multiplayer χαρακτιριστηκα προσεχως!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Οδηγός"
     tome_minion_spells: "Ξόρκια για τα τσιράκια σας"
     tome_read_only_spells: "Ξορκια μονο για αναγνωση"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "ελληνικά", englishDescription: "Gre
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/en-AU.coffee b/app/locale/en-AU.coffee
index 9d73b3a95..a9574e5ad 100644
--- a/app/locale/en-AU.coffee
+++ b/app/locale/en-AU.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "English (AU)", englishDescription: "English
 #    multiplayer_hint_label: "Hint:"
 #    multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
 #    multiplayer_coming_soon: "More multiplayer features to come!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
 #    guide_title: "Guide"
 #    tome_minion_spells: "Your Minions' Spells"
 #    tome_read_only_spells: "Read-Only Spells"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "English (AU)", englishDescription: "English
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/en-GB.coffee b/app/locale/en-GB.coffee
index cd8995418..f3dc42c6d 100644
--- a/app/locale/en-GB.coffee
+++ b/app/locale/en-GB.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "English (UK)", englishDescription: "English
 #    multiplayer_hint_label: "Hint:"
 #    multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
 #    multiplayer_coming_soon: "More multiplayer features to come!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
 #    guide_title: "Guide"
 #    tome_minion_spells: "Your Minions' Spells"
 #    tome_read_only_spells: "Read-Only Spells"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "English (UK)", englishDescription: "English
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/en-US.coffee b/app/locale/en-US.coffee
index 640cbee96..874cd9063 100644
--- a/app/locale/en-US.coffee
+++ b/app/locale/en-US.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "English (US)", englishDescription: "English
 #    multiplayer_hint_label: "Hint:"
 #    multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
 #    multiplayer_coming_soon: "More multiplayer features to come!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
 #    guide_title: "Guide"
 #    tome_minion_spells: "Your Minions' Spells"
 #    tome_read_only_spells: "Read-Only Spells"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "English (US)", englishDescription: "English
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/en.coffee b/app/locale/en.coffee
index abf1a2fd1..b8c3d7707 100644
--- a/app/locale/en.coffee
+++ b/app/locale/en.coffee
@@ -712,3 +712,14 @@
     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
+
+  delta:
+    added: "Added"
+    modified: "Modified"
+    deleted: "Deleted"
+    moved_index: "Moved Index"
+    text_diff: "Text Diff"
+    merge_conflict_with: "MERGE CONFLICT WITH"
+    no_changes: "No Changes"
+    
diff --git a/app/locale/es-419.coffee b/app/locale/es-419.coffee
index 660719a22..a9e351132 100644
--- a/app/locale/es-419.coffee
+++ b/app/locale/es-419.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "español (América Latina)", englishDescrip
     multiplayer_hint_label: "Consejo:"
     multiplayer_hint: " Cliquea el enlace para seleccionar todo, luego presiona ⌘-C o Ctrl-C para copiar el enlace."
     multiplayer_coming_soon: "¡Más características de multijugador por venir!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Guía"
     tome_minion_spells: "Hechizos de tus Secuaces"
     tome_read_only_spells: "Hechizos de Sólo Lectura"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "español (América Latina)", englishDescrip
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/es-ES.coffee b/app/locale/es-ES.coffee
index ee6166771..632d9aeec 100644
--- a/app/locale/es-ES.coffee
+++ b/app/locale/es-ES.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
     multiplayer_hint_label: "Pista:"
     multiplayer_hint: " Haz un click en el link para que se seleccione, después utiliza Ctrl-C o ⌘-C para copiar el link."
     multiplayer_coming_soon: "¡Más opciones de Multijugador están por venir!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Guía"
     tome_minion_spells: "Los hechizos de tus súbditos"
     tome_read_only_spells: "Hechizos de solo lectura"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/es.coffee b/app/locale/es.coffee
index d13ab8697..19a13448f 100644
--- a/app/locale/es.coffee
+++ b/app/locale/es.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "español", englishDescription: "Spanish", t
     multiplayer_hint_label: "Consejo:"
     multiplayer_hint: " Cliquea el enlace para seleccionar todo, luego presiona ⌘-C o Ctrl-C para copiar el enlace."
     multiplayer_coming_soon: "¡Más características de multijugador por venir!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Guía"
     tome_minion_spells: "Hechizos de tus Secuaces"
     tome_read_only_spells: "Hechizos de Sólo Lectura"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "español", englishDescription: "Spanish", t
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/fa.coffee b/app/locale/fa.coffee
index 404fb28cd..e733bf95e 100644
--- a/app/locale/fa.coffee
+++ b/app/locale/fa.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "فارسی", englishDescription: "Persian",
 #    multiplayer_hint_label: "Hint:"
 #    multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
 #    multiplayer_coming_soon: "More multiplayer features to come!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
 #    guide_title: "Guide"
 #    tome_minion_spells: "Your Minions' Spells"
 #    tome_read_only_spells: "Read-Only Spells"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "فارسی", englishDescription: "Persian",
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/fi.coffee b/app/locale/fi.coffee
index 448cf6899..974751c43 100644
--- a/app/locale/fi.coffee
+++ b/app/locale/fi.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "suomi", englishDescription: "Finnish", tran
 #    multiplayer_hint_label: "Hint:"
 #    multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
 #    multiplayer_coming_soon: "More multiplayer features to come!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
 #    guide_title: "Guide"
 #    tome_minion_spells: "Your Minions' Spells"
 #    tome_read_only_spells: "Read-Only Spells"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "suomi", englishDescription: "Finnish", tran
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/fr.coffee b/app/locale/fr.coffee
index 2b510dd04..1c76f3ccd 100644
--- a/app/locale/fr.coffee
+++ b/app/locale/fr.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
     multiplayer_hint_label: "Astuce:"
     multiplayer_hint: " Cliquez sur le lien pour tout sélectionner, puis appuyer sur Pomme-C ou Ctrl-C pour copier le lien."
     multiplayer_coming_soon: "Plus de fonctionnalités multijoueurs sont à venir"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Guide"
     tome_minion_spells: "Les sorts de vos soldats"
     tome_read_only_spells: "Sorts en lecture-seule"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/he.coffee b/app/locale/he.coffee
index 77f17163c..2df5729cc 100644
--- a/app/locale/he.coffee
+++ b/app/locale/he.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "עברית", englishDescription: "Hebrew",
 #    multiplayer_hint_label: "Hint:"
 #    multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
 #    multiplayer_coming_soon: "More multiplayer features to come!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
 #    guide_title: "Guide"
 #    tome_minion_spells: "Your Minions' Spells"
 #    tome_read_only_spells: "Read-Only Spells"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "עברית", englishDescription: "Hebrew",
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/hi.coffee b/app/locale/hi.coffee
index dd0fc9ea0..d8d8ee3af 100644
--- a/app/locale/hi.coffee
+++ b/app/locale/hi.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "मानक हिन्दी", englishDe
 #    multiplayer_hint_label: "Hint:"
 #    multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
 #    multiplayer_coming_soon: "More multiplayer features to come!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
 #    guide_title: "Guide"
 #    tome_minion_spells: "Your Minions' Spells"
 #    tome_read_only_spells: "Read-Only Spells"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "मानक हिन्दी", englishDe
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/hu.coffee b/app/locale/hu.coffee
index 9f1258214..0c2c6d55c 100644
--- a/app/locale/hu.coffee
+++ b/app/locale/hu.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "magyar", englishDescription: "Hungarian", t
     multiplayer_hint_label: "Tipp:"
     multiplayer_hint: " Kattints a linkre, és Ctrl+C-vel (vagy ⌘+C-vel) másold a vágólapra!"
 #    multiplayer_coming_soon: "More multiplayer features to come!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Útmutató"
     tome_minion_spells: "Egységeid varázslatai"
     tome_read_only_spells: "Csak olvasható varázslatok"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "magyar", englishDescription: "Hungarian", t
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/id.coffee b/app/locale/id.coffee
index 7de47c188..93fb972d3 100644
--- a/app/locale/id.coffee
+++ b/app/locale/id.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "Bahasa Indonesia", englishDescription: "Ind
 #    multiplayer_hint_label: "Hint:"
 #    multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
 #    multiplayer_coming_soon: "More multiplayer features to come!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
 #    guide_title: "Guide"
 #    tome_minion_spells: "Your Minions' Spells"
 #    tome_read_only_spells: "Read-Only Spells"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "Bahasa Indonesia", englishDescription: "Ind
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/it.coffee b/app/locale/it.coffee
index 1073eeb5d..6ca663b43 100644
--- a/app/locale/it.coffee
+++ b/app/locale/it.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "Italiano", englishDescription: "Italian", t
     multiplayer_hint_label: "Suggerimento:"
     multiplayer_hint: " Clicca il link per selezionare tutto, quindi premi CMD-C o Ctrl-C per copiare il link."
     multiplayer_coming_soon: "Ulteriori aggiunte per il multigiocatore in arrivo!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Guida"
     tome_minion_spells: "Incantesimi dei tuoi seguaci"
     tome_read_only_spells: "Incantesimi in sola lettura"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "Italiano", englishDescription: "Italian", t
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/ja.coffee b/app/locale/ja.coffee
index c0933ed0f..bd68e1aa9 100644
--- a/app/locale/ja.coffee
+++ b/app/locale/ja.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
     multiplayer_hint_label: "ヒント:"
     multiplayer_hint: " リンクを選択後、 ⌘-C(MacOS) or Ctrl-C(Windows) でリンクをコピーできます。"
     multiplayer_coming_soon: "今後より多くのマルチプレイ機能が追加されます。"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "ガイド"
     tome_minion_spells: "操作できるキャラクターの呪文"
     tome_read_only_spells: "読込専用の呪文"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/ko.coffee b/app/locale/ko.coffee
index 3b0ba73d7..e4145bc72 100644
--- a/app/locale/ko.coffee
+++ b/app/locale/ko.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "한국어", englishDescription: "Korean", t
     multiplayer_hint_label: "힌트:"
     multiplayer_hint: " 모두 선택하려면 링크를 클릭하세요, 그리고 ⌘-C 또는 Ctrl-C 를 눌러서 링크를 복사하세요."
     multiplayer_coming_soon: "곧 좀 더 다양한 멀티플레이어 모드가 업데이트 됩니다!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "가이드"
     tome_minion_spells: "당신 미니언의' 마법"
     tome_read_only_spells: "읽기 전용 마법"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "한국어", englishDescription: "Korean", t
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/lt.coffee b/app/locale/lt.coffee
index 64864930f..31749c727 100644
--- a/app/locale/lt.coffee
+++ b/app/locale/lt.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "lietuvių kalba", englishDescription: "Lith
 #    multiplayer_hint_label: "Hint:"
 #    multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
 #    multiplayer_coming_soon: "More multiplayer features to come!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
 #    guide_title: "Guide"
 #    tome_minion_spells: "Your Minions' Spells"
 #    tome_read_only_spells: "Read-Only Spells"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "lietuvių kalba", englishDescription: "Lith
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/ms.coffee b/app/locale/ms.coffee
index 3fbc839f7..1316bbe2a 100644
--- a/app/locale/ms.coffee
+++ b/app/locale/ms.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "Bahasa Melayu", englishDescription: "Bahasa
 #    multiplayer_hint_label: "Hint:"
 #    multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
 #    multiplayer_coming_soon: "More multiplayer features to come!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
 #    guide_title: "Guide"
 #    tome_minion_spells: "Your Minions' Spells"
 #    tome_read_only_spells: "Read-Only Spells"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "Bahasa Melayu", englishDescription: "Bahasa
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/nb.coffee b/app/locale/nb.coffee
index 7f7604c7a..8ce394170 100644
--- a/app/locale/nb.coffee
+++ b/app/locale/nb.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
     multiplayer_hint_label: "Hint:"
     multiplayer_hint: " Klikk lenken for å velge alle, så trykker du Apple-C eller Ctrl-C for å kopiere lenken."
     multiplayer_coming_soon: "Det kommer flere flerspillsmuligheter!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Guide"
     tome_minion_spells: "Din Minions' Trylleformularer"
     tome_read_only_spells: "Kun-Lesbare Trylleformularer"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/nl-BE.coffee b/app/locale/nl-BE.coffee
index 7c65bdb53..e89cbd30e 100644
--- a/app/locale/nl-BE.coffee
+++ b/app/locale/nl-BE.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "Nederlands (België)", englishDescription:
     multiplayer_hint_label: "Hint:"
     multiplayer_hint: " Klik de link om alles te selecteren, druk dan op Apple-C of Ctrl-C om de link te kopiëren."
     multiplayer_coming_soon: "Binnenkort komen er meer Multiplayermogelijkheden!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Handleiding"
     tome_minion_spells: "Jouw Minions' Spreuken"
     tome_read_only_spells: "Read-Only Spreuken"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "Nederlands (België)", englishDescription:
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/nl-NL.coffee b/app/locale/nl-NL.coffee
index 66ebdec3f..4ce7aa839 100644
--- a/app/locale/nl-NL.coffee
+++ b/app/locale/nl-NL.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
     multiplayer_hint_label: "Hint:"
     multiplayer_hint: " Klik de link om alles te selecteren, druk dan op Apple-C of Ctrl-C om de link te kopiëren."
     multiplayer_coming_soon: "Binnenkort komen er meer Multiplayermogelijkheden!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Handleiding"
     tome_minion_spells: "Jouw Minions' Spreuken"
     tome_read_only_spells: "Read-Only Spreuken"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/nl.coffee b/app/locale/nl.coffee
index f749e910f..8fbf7075b 100644
--- a/app/locale/nl.coffee
+++ b/app/locale/nl.coffee
@@ -16,7 +16,7 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
     play: "Spelen"
     retry: "Probeer opnieuw"
     watch: "Volgen"
-    unwatch: "Ontvolgen" 
+    unwatch: "Ontvolgen"
     submit_patch: "Correctie Opsturen"
 
   units:
@@ -199,7 +199,7 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
 
   employers:
     want_to_hire_our_players: "Wil je expert CodeCombat spelers aanwerven? "
-    see_candidates: "Klik om je kandidaten te zien" 
+    see_candidates: "Klik om je kandidaten te zien"
     candidates_count_prefix: "Momenteel hebben we "
     candidates_count_many: "veel"
     candidates_count_suffix: "zeer getalenteerde en ervaren ontwikkelaars die werk zoeken."
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
     multiplayer_hint_label: "Hint:"
     multiplayer_hint: " Klik de link om alles te selecteren, druk dan op Apple-C of Ctrl-C om de link te kopiëren."
     multiplayer_coming_soon: "Binnenkort komen er meer Multiplayermogelijkheden!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Handleiding"
     tome_minion_spells: "Jouw Minions' Spreuken"
     tome_read_only_spells: "Read-Only Spreuken"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
     user_names: "Gebruikersnamen"
     files: "Bestanden"
     top_simulators: "Top Simulatoren"
+#    source_document: "Source Document"
diff --git a/app/locale/nn.coffee b/app/locale/nn.coffee
index 5a6fabbc3..7dcf52989 100644
--- a/app/locale/nn.coffee
+++ b/app/locale/nn.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "Norwegian Nynorsk", englishDescription: "No
 #    multiplayer_hint_label: "Hint:"
 #    multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
 #    multiplayer_coming_soon: "More multiplayer features to come!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
 #    guide_title: "Guide"
 #    tome_minion_spells: "Your Minions' Spells"
 #    tome_read_only_spells: "Read-Only Spells"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "Norwegian Nynorsk", englishDescription: "No
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/no.coffee b/app/locale/no.coffee
index 6f8e0c22c..3d4984583 100644
--- a/app/locale/no.coffee
+++ b/app/locale/no.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "Norsk", englishDescription: "Norwegian", tr
     multiplayer_hint_label: "Hint:"
     multiplayer_hint: " Klikk lenken for å velge alle, så trykker du Apple-C eller Ctrl-C for å kopiere lenken."
     multiplayer_coming_soon: "Det kommer flere flerspillsmuligheter!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Guide"
     tome_minion_spells: "Din Minions' Trylleformularer"
     tome_read_only_spells: "Kun-Lesbare Trylleformularer"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "Norsk", englishDescription: "Norwegian", tr
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/pl.coffee b/app/locale/pl.coffee
index d035da233..8e4e0dc89 100644
--- a/app/locale/pl.coffee
+++ b/app/locale/pl.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "język polski", englishDescription: "Polish
     multiplayer_hint_label: "Podpowiedź:"
     multiplayer_hint: "Kliknij link by zaznaczyć wszystko, potem wciśnij Cmd-C lub Ctrl-C by skopiować ten link."
     multiplayer_coming_soon: "Wkrótce więcej opcji multiplayer"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Przewodnik"
     tome_minion_spells: "Czary twojego podopiecznego"
     tome_read_only_spells: "Czary tylko do odczytu"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "język polski", englishDescription: "Polish
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/pt-BR.coffee b/app/locale/pt-BR.coffee
index 53dc7cd4f..0d30c42c8 100644
--- a/app/locale/pt-BR.coffee
+++ b/app/locale/pt-BR.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
     multiplayer_hint_label: "Dica:"
     multiplayer_hint: " Clique no link para selecionar tudo, então dê Ctrl+C ou ⌘+C para copiar o link. "
     multiplayer_coming_soon: "Mais novidades no multiplayer estão chegando!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Guia"
     tome_minion_spells: "Magias dos seus subordinados"
     tome_read_only_spells: "Magias não editáveis"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/pt-PT.coffee b/app/locale/pt-PT.coffee
index 3a7a84bac..26be267f8 100644
--- a/app/locale/pt-PT.coffee
+++ b/app/locale/pt-PT.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "Português europeu", englishDescription: "P
     multiplayer_hint_label: "Dica:"
     multiplayer_hint: " Carrega no link para seleccionar tudp, depois pressiona ⌘-C ou Ctrl-C para copiar o link."
     multiplayer_coming_soon: "Mais funcionalidades de multiplayer brevemente!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Guia"
     tome_minion_spells: "Feitiços dos teus Minions"
     tome_read_only_spells: "Feitiços apenas de leitura"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "Português europeu", englishDescription: "P
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/pt.coffee b/app/locale/pt.coffee
index de356491c..1d742cd0d 100644
--- a/app/locale/pt.coffee
+++ b/app/locale/pt.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "português", englishDescription: "Portugues
     multiplayer_hint_label: "Dica:"
     multiplayer_hint: " Clique no link para selecionar tudo, então dê Ctrl+C ou ⌘+C para copiar o link. "
     multiplayer_coming_soon: "Mais novidades no multiplayer estão chegando!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Guia"
     tome_minion_spells: "Magias dos seus subordinados"
     tome_read_only_spells: "Magias não editáveis"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "português", englishDescription: "Portugues
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/ro.coffee b/app/locale/ro.coffee
index 1bb1a7342..d8701339f 100644
--- a/app/locale/ro.coffee
+++ b/app/locale/ro.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "limba română", englishDescription: "Roman
     multiplayer_hint_label: "Hint:"
     multiplayer_hint: " Apasă pe link pentru a selecta tot, apoi apasă ⌘-C sau Ctrl-C pentru a copia link-ul."
     multiplayer_coming_soon: "Mai multe feature-uri multiplayer în curând!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Ghid"
     tome_minion_spells: "Vrăjile Minion-ilor tăi"
     tome_read_only_spells: "Vrăji Read-Only"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "limba română", englishDescription: "Roman
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/ru.coffee b/app/locale/ru.coffee
index fc3c6e5a4..2b3bfd5fb 100644
--- a/app/locale/ru.coffee
+++ b/app/locale/ru.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
     multiplayer_hint_label: "Подсказка: "
     multiplayer_hint: "кликните на ссылку, чтобы выделить её, затем нажмите ⌘-С или Ctrl-C, чтобы скопировать."
     multiplayer_coming_soon: "Больше возможностей мультиплеера на подходе!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Руководство"
     tome_minion_spells: "Заклинания ваших миньонов"
     tome_read_only_spells: "Заклинания только для чтения"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/sk.coffee b/app/locale/sk.coffee
index decaf66c2..c4148ea16 100644
--- a/app/locale/sk.coffee
+++ b/app/locale/sk.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "slovenčina", englishDescription: "Slovak",
 #    multiplayer_hint_label: "Hint:"
 #    multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
 #    multiplayer_coming_soon: "More multiplayer features to come!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
 #    guide_title: "Guide"
 #    tome_minion_spells: "Your Minions' Spells"
 #    tome_read_only_spells: "Read-Only Spells"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "slovenčina", englishDescription: "Slovak",
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/sl.coffee b/app/locale/sl.coffee
index d56856f20..aaea22579 100644
--- a/app/locale/sl.coffee
+++ b/app/locale/sl.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "slovenščina", englishDescription: "Sloven
 #    multiplayer_hint_label: "Hint:"
 #    multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
 #    multiplayer_coming_soon: "More multiplayer features to come!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
 #    guide_title: "Guide"
 #    tome_minion_spells: "Your Minions' Spells"
 #    tome_read_only_spells: "Read-Only Spells"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "slovenščina", englishDescription: "Sloven
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/sr.coffee b/app/locale/sr.coffee
index b309b5d0a..264f7f92b 100644
--- a/app/locale/sr.coffee
+++ b/app/locale/sr.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
     multiplayer_hint_label: "Мала помоћ"
     multiplayer_hint: " Кликни на линк да обележиш све, затим притисни Apple-C или Ctrl-C да копираш линк."
     multiplayer_coming_soon: "Стиже још нових карактеристика!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Водич"
     tome_minion_spells: "Чини твојих поданика"
     tome_read_only_spells: "Чини које се могу само гледати"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/sv.coffee b/app/locale/sv.coffee
index f37b7d57b..c783521f5 100644
--- a/app/locale/sv.coffee
+++ b/app/locale/sv.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "Svenska", englishDescription: "Swedish", tr
     multiplayer_hint_label: "Tips:"
     multiplayer_hint: " Klicka på länken för att välja allt, tryck sedan på Cmd-C eller Ctrl-C för att kopiera länken."
     multiplayer_coming_soon: "Fler flerspelarlägen kommer!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Guide"
     tome_minion_spells: "Dina soldaters förmågor"
     tome_read_only_spells: "Skrivskyddade förmågor"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "Svenska", englishDescription: "Swedish", tr
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/th.coffee b/app/locale/th.coffee
index 49b2b5285..513bae855 100644
--- a/app/locale/th.coffee
+++ b/app/locale/th.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "ไทย", englishDescription: "Thai", tra
     multiplayer_hint_label: "คำใบ้"
 #    multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
 #    multiplayer_coming_soon: "More multiplayer features to come!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
 #    guide_title: "Guide"
 #    tome_minion_spells: "Your Minions' Spells"
 #    tome_read_only_spells: "Read-Only Spells"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "ไทย", englishDescription: "Thai", tra
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/tr.coffee b/app/locale/tr.coffee
index e4b9b9496..8b1ef047a 100644
--- a/app/locale/tr.coffee
+++ b/app/locale/tr.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "Türkçe", englishDescription: "Turkish", t
     multiplayer_hint_label: "İpucu:"
     multiplayer_hint: " Kopyalamak için önce linke tıklayın, ardından CTRL+C veya ⌘+C kombinasyonuna basın."
     multiplayer_coming_soon: "Daha bir çok çoklu oyuncu özelliği eklenecek!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Rehber"
     tome_minion_spells: "Minyonlarınızın Büyüleri"
     tome_read_only_spells: "Salt Okunur Büyüler"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "Türkçe", englishDescription: "Turkish", t
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/uk.coffee b/app/locale/uk.coffee
index 752827cde..b2d544530 100644
--- a/app/locale/uk.coffee
+++ b/app/locale/uk.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "українська мова", englishDesc
     multiplayer_hint_label: "Підказка:"
     multiplayer_hint: "Натисніть на посилання, щоб обрати всіх, та натисніть Apple-C або Ctrl-C, щоб скопіювати посилання."
     multiplayer_coming_soon: "Скоро - більше можливостей у мультиплеєрі!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "Посібник"
     tome_minion_spells: "Закляття ваших міньонів"
     tome_read_only_spells: "Закляття тільки для читання"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "українська мова", englishDesc
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/ur.coffee b/app/locale/ur.coffee
index dea4a72a2..ce51b341d 100644
--- a/app/locale/ur.coffee
+++ b/app/locale/ur.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "اُردُو", englishDescription: "Urdu",
 #    multiplayer_hint_label: "Hint:"
 #    multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
 #    multiplayer_coming_soon: "More multiplayer features to come!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
 #    guide_title: "Guide"
 #    tome_minion_spells: "Your Minions' Spells"
 #    tome_read_only_spells: "Read-Only Spells"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "اُردُو", englishDescription: "Urdu",
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/vi.coffee b/app/locale/vi.coffee
index 898291cfb..3a243c5d7 100644
--- a/app/locale/vi.coffee
+++ b/app/locale/vi.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
 #    multiplayer_hint_label: "Hint:"
 #    multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
 #    multiplayer_coming_soon: "More multiplayer features to come!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
 #    guide_title: "Guide"
 #    tome_minion_spells: "Your Minions' Spells"
 #    tome_read_only_spells: "Read-Only Spells"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/zh-HANS.coffee b/app/locale/zh-HANS.coffee
index 364483ca9..73198457c 100644
--- a/app/locale/zh-HANS.coffee
+++ b/app/locale/zh-HANS.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
     multiplayer_hint_label: "提示:"
     multiplayer_hint: " 点击全选,然后按 Apple-C(苹果电脑)或 Ctrl-C 复制链接。"
     multiplayer_coming_soon: "多人游戏的更多特性!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "指南"
     tome_minion_spells: "助手的咒语"
     tome_read_only_spells: "只读的咒语"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/zh-HANT.coffee b/app/locale/zh-HANT.coffee
index 0972a07f8..ed6c76e07 100644
--- a/app/locale/zh-HANT.coffee
+++ b/app/locale/zh-HANT.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "繁体中文", englishDescription: "Chinese
     multiplayer_hint_label: "提示:"
     multiplayer_hint: " 點擊全選,然後按 ⌘-C 或 Ctrl-C 複製連結。"
     multiplayer_coming_soon: "請期待更多的多人關卡!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "指南"
     tome_minion_spells: "助手的咒語"
     tome_read_only_spells: "唯讀的咒語"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "繁体中文", englishDescription: "Chinese
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/zh-WUU-HANS.coffee b/app/locale/zh-WUU-HANS.coffee
index 33070e062..04f6deb48 100644
--- a/app/locale/zh-WUU-HANS.coffee
+++ b/app/locale/zh-WUU-HANS.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "吴语", englishDescription: "Wuu (Simplifi
 #    multiplayer_hint_label: "Hint:"
 #    multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
 #    multiplayer_coming_soon: "More multiplayer features to come!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
 #    guide_title: "Guide"
 #    tome_minion_spells: "Your Minions' Spells"
 #    tome_read_only_spells: "Read-Only Spells"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "吴语", englishDescription: "Wuu (Simplifi
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/zh-WUU-HANT.coffee b/app/locale/zh-WUU-HANT.coffee
index b7dd0f40a..410d45285 100644
--- a/app/locale/zh-WUU-HANT.coffee
+++ b/app/locale/zh-WUU-HANT.coffee
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "吳語", englishDescription: "Wuu (Traditio
     multiplayer_hint_label: "提醒:"
     multiplayer_hint: " 點牢全選,再捺 Apple-C(蘋果電腦)要勿 Ctrl-C 複製鏈接。"
     multiplayer_coming_soon: "多人遊戲還多特性!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
     guide_title: "指南"
     tome_minion_spells: "下手個咒語"
     tome_read_only_spells: "只讀個咒語"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "吳語", englishDescription: "Wuu (Traditio
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/locale/zh.coffee b/app/locale/zh.coffee
index b3c8ac1df..22e226cd7 100644
--- a/app/locale/zh.coffee
+++ b/app/locale/zh.coffee
@@ -17,7 +17,7 @@ module.exports = nativeDescription: "中文", englishDescription: "Chinese", tra
     retry: "重试"
 #    watch: "Watch"
 #    unwatch: "Unwatch"
-#    submit_patch: "Submit Patch"
+    submit_patch: "提交补丁"
 
   units:
     second: "秒"
@@ -36,7 +36,7 @@ module.exports = nativeDescription: "中文", englishDescription: "Chinese", tra
 
   nav:
     play: "玩"
-#    community: "Community"
+    community: "社区"
     editor: "编辑"
     blog: "博客"
     forum: "论坛"
@@ -53,7 +53,7 @@ module.exports = nativeDescription: "中文", englishDescription: "Chinese", tra
   versions:
     save_version_title: "保存新版本"
     new_major_version: "最新主要版本"
-#    cla_prefix: "To save changes, first you must agree to our"
+    cla_prefix: "要保存更改, 首先你必须要统一我们的"
 #    cla_url: "CLA"
 #    cla_suffix: "."
     cla_agree: "我同意"
@@ -61,7 +61,7 @@ module.exports = nativeDescription: "中文", englishDescription: "Chinese", tra
   login:
     sign_up: "注册"
     log_in: "登录"
-#    logging_in: "Logging In"
+    logging_in: "登录中..."
     log_out: "登出"
     recover: "找回账户"
 
@@ -78,7 +78,7 @@ module.exports = nativeDescription: "中文", englishDescription: "Chinese", tra
     creating: "账户在创新中"
     sign_up: "注册"
     log_in: "以密码登录"
-#    social_signup: "Or, you can sign up through Facebook or G+:"
+    social_signup: "或者,  你可以通过Facebook 或者 G+ 注册:"
 
   home:
     slogan: "通过游戏学习Javascript脚本语言"
@@ -130,21 +130,21 @@ module.exports = nativeDescription: "中文", englishDescription: "Chinese", tra
 #    learn_more: "Learn more about being a Diplomat"
 #    subscribe_as_diplomat: "Subscribe as a Diplomat"
 
-#  wizard_settings:
-#    title: "Wizard Settings"
-#    customize_avatar: "Customize Your Avatar"
-#    active: "Active"
-#    color: "Color"
-#    group: "Group"
-#    clothes: "Clothes"
-#    trim: "Trim"
-#    cloud: "Cloud"
-#    team: "Team"
-#    spell: "Spell"
-#    boots: "Boots"
-#    hue: "Hue"
-#    saturation: "Saturation"
-#    lightness: "Lightness"
+  wizard_settings:
+    title: "巫师设定"
+    customize_avatar: "设置你的头像"
+    active: "启用"
+    color: "颜色"
+    group: "类别"
+    clothes: "衣服"
+    trim: "条纹"
+    cloud: "云"
+    team: "队伍"
+    spell: "魔法球"
+    boots: "鞋子"
+    hue: "色彩"
+    saturation: "饱和度"
+    lightness: "亮度"
 
 #  account_settings:
 #    title: "Account Settings"
@@ -246,6 +246,7 @@ module.exports = nativeDescription: "中文", englishDescription: "Chinese", tra
 #    multiplayer_hint_label: "Hint:"
 #    multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
 #    multiplayer_coming_soon: "More multiplayer features to come!"
+#    multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
 #    guide_title: "Guide"
 #    tome_minion_spells: "Your Minions' Spells"
 #    tome_read_only_spells: "Read-Only Spells"
@@ -710,3 +711,4 @@ module.exports = nativeDescription: "中文", englishDescription: "Chinese", tra
 #    user_names: "User Names"
 #    files: "Files"
 #    top_simulators: "Top Simulators"
+#    source_document: "Source Document"
diff --git a/app/models/CocoModel.coffee b/app/models/CocoModel.coffee
index a7d408adf..75d0172dd 100644
--- a/app/models/CocoModel.coffee
+++ b/app/models/CocoModel.coffee
@@ -1,6 +1,5 @@
 storage = require 'lib/storage'
 deltasLib = require 'lib/deltas'
-auth = require 'lib/auth'
 
 class CocoModel extends Backbone.Model
   idAttribute: "_id"
@@ -9,6 +8,8 @@ class CocoModel extends Backbone.Model
   saveBackups: false
   @schema: null
 
+  getMe: -> @me or @me = require('lib/auth').me
+
   initialize: ->
     super()
     if not @constructor.className
@@ -27,7 +28,7 @@ class CocoModel extends Backbone.Model
     clone = super()
     clone.set($.extend(true, {}, if withChanges then @attributes else @_revertAttributes))
     clone
-    
+
   onError: ->
     @loading = false
 
@@ -96,7 +97,8 @@ class CocoModel extends Backbone.Model
     not _.isEqual @attributes, @_revertAttributes
 
   cloneNewMinorVersion: ->
-    newData = $.extend(null, {}, @attributes)
+    newData = _.clone @attributes
+
     clone = new @constructor(newData)
     clone
 
@@ -136,7 +138,7 @@ class CocoModel extends Backbone.Model
   hasReadAccess: (actor) ->
     # actor is a User object
 
-    actor ?= auth.me
+    actor ?= @getMe()
     return true if actor.isAdmin()
     if @get('permissions')?
       for permission in @get('permissions')
@@ -148,7 +150,7 @@ class CocoModel extends Backbone.Model
   hasWriteAccess: (actor) ->
     # actor is a User object
 
-    actor ?= auth.me
+    actor ?= @getMe()
     return true if actor.isAdmin()
     if @get('permissions')?
       for permission in @get('permissions')
@@ -160,6 +162,10 @@ class CocoModel extends Backbone.Model
   getDelta: ->
     differ = deltasLib.makeJSONDiffer()
     differ.diff @_revertAttributes, @attributes
+    
+  getDeltaWith: (otherModel) ->
+    differ = deltasLib.makeJSONDiffer()
+    differ.diff @attributes, otherModel.attributes
 
   applyDelta: (delta) ->
     newAttributes = $.extend(true, {}, @attributes)
@@ -170,13 +176,17 @@ class CocoModel extends Backbone.Model
     delta = @getDelta()
     deltasLib.expandDelta(delta, @_revertAttributes, @schema())
 
+  getExpandedDeltaWith: (otherModel) ->
+    delta = @getDeltaWith(otherModel)
+    deltasLib.expandDelta(delta, @attributes, @schema())
+
   watch: (doWatch=true) ->
     $.ajax("#{@urlRoot}/#{@id}/watch", {type:'PUT', data:{on:doWatch}})
     @watching = -> doWatch
 
   watching: ->
     return me.id in (@get('watchers') or [])
-    
+
   populateI18N: (data, schema, path='') ->
     # TODO: Better schema/json walking
     sum = 0
@@ -185,7 +195,7 @@ class CocoModel extends Backbone.Model
     if schema.properties?.i18n and _.isPlainObject(data) and not data.i18n?
       data.i18n = {}
       sum += 1
-      
+
     if _.isPlainObject data
       for key, value of data
         numChanged = 0
@@ -193,10 +203,10 @@ class CocoModel extends Backbone.Model
         if numChanged and not path # should only do this for the root object
           @set key, value
         sum += numChanged
-          
+
     if schema.items and _.isArray data
       sum += @populateI18N(value, schema.items, path+'/'+index) for value, index in data
-    
+
     sum
 
   @getReferencedModel: (data, schema) ->
@@ -227,13 +237,13 @@ class CocoModel extends Backbone.Model
     model = new Model()
     model.url = makeUrlFunc(link)
     return model
-    
+
   setURL: (url) ->
     makeURLFunc = (u) -> -> u
     @url = makeURLFunc(url)
     @
-    
+
   getURL: ->
     return if _.isString @url then @url else @url()
-    
+
 module.exports = CocoModel
diff --git a/app/models/SuperModel.coffee b/app/models/SuperModel.coffee
index 204193659..465e2b17b 100644
--- a/app/models/SuperModel.coffee
+++ b/app/models/SuperModel.coffee
@@ -31,8 +31,9 @@ module.exports = class SuperModel extends Backbone.Model
 
     else
       @registerModel(model)
-      console.debug 'Registering model', model.getURL()
-      return @addModelResource(model, name, fetchOptions, value).load()
+      res = @addModelResource(model, name, fetchOptions, value)
+      if model.loaded then res.markLoaded() else res.load()
+      return res
 
   loadCollection: (collection, name, fetchOptions, value=1) ->
     url = collection.getURL()
@@ -52,7 +53,9 @@ module.exports = class SuperModel extends Backbone.Model
       @listenToOnce collection, 'sync', (c) ->
         console.debug 'Registering collection', url
         @registerCollection c
-      return @addModelResource(collection, name, fetchOptions, value).load()
+      res = @addModelResource(collection, name, fetchOptions, value)
+      res.load() if not (res.isLoading or res.isLoaded)
+      return res
 
   # replace or overwrite
   shouldSaveBackups: (model) -> false
diff --git a/app/styles/editor/level/component/edit.sass b/app/styles/editor/level/component/edit.sass
index 7a78534ed..e4301dbde 100644
--- a/app/styles/editor/level/component/edit.sass
+++ b/app/styles/editor/level/component/edit.sass
@@ -5,7 +5,10 @@
   #component-patches
     padding: 0 10px 10px
     background: white
-  
+
+  .patches-view
+    padding: 10px 20px 10px 0px
+
   .navbar-text
     float: left
     
diff --git a/app/styles/editor/level/system/edit.sass b/app/styles/editor/level/system/edit.sass
index 567ff5b27..f8ca710d1 100644
--- a/app/styles/editor/level/system/edit.sass
+++ b/app/styles/editor/level/system/edit.sass
@@ -1,4 +1,14 @@
 #editor-level-system-edit-view
+  nav
+    margin-bottom: 0
+    
+  #system-patches
+    padding: 0 10px 10px
+    background: white
+
+  .patches-view
+    padding: 10px 20px 10px 0px
+
   .navbar-text
     float: left
 
diff --git a/app/templates/account/profile.jade b/app/templates/account/profile.jade
index 8f3af794e..aedd0d623 100644
--- a/app/templates/account/profile.jade
+++ b/app/templates/account/profile.jade
@@ -17,7 +17,7 @@ block content
         if user.id != me.id
           button.btn.edit-settings-button#enter-espionage-mode 007
   
-  if user.get('jobProfile')
+  if user.get('jobProfile') && allowedToViewJobProfile
     - var profile = user.get('jobProfile');
     .job-profile-container
       .job-profile-row
@@ -112,7 +112,11 @@ block content
                         .project-image(style="background-image: url('/file/" + project.picture + "')")
                       p= project.name
                       div!= marked(project.description)
-
+  else if allowedToViewJobProfile
+    .public-profile-container
+      h2 Loading...
+      
+                        
   else
     .public-profile-container
       h2 
diff --git a/app/templates/editor/delta.jade b/app/templates/editor/delta.jade
index 480e4ef01..60b95cc79 100644
--- a/app/templates/editor/delta.jade
+++ b/app/templates/editor/delta.jade
@@ -37,10 +37,12 @@ mixin deltaPanel(delta, conflict)
   
     if delta.conflict && !conflict
       .panel-body
-        strong MERGE CONFLICT WITH
+        strong(data-i18n="delta.merge_conflict_with") MERGE CONFLICT WITH
         +deltaPanel(delta.conflict, true)
 
 .panel-group(id='delta-accordion-'+(counter))
   for delta in deltas
     +deltaPanel(delta)
+  if !deltas.length
+    alert.alert-warning(data-i18n="delta.no_changes") No changes
     
\ No newline at end of file
diff --git a/app/templates/editor/level/edit.jade b/app/templates/editor/level/edit.jade
index 415ff9e20..c2dfbac6f 100644
--- a/app/templates/editor/level/edit.jade
+++ b/app/templates/editor/level/edit.jade
@@ -24,7 +24,7 @@ block header
         li
           a(href="#editor-level-settings-tab-view", data-toggle="tab", data-i18n="editor.level_tab_settings") Settings
         li
-          a(href="#editor-level-components-tab-view", data-toggle="tab", data-i18n="editor.level_tab_components") Components
+          a(href="#editor-level-components-tab-view", data-toggle="tab", data-i18n="editor.level_tab_components")#components-tab Components
         li
           a(href="#editor-level-systems-tab-view", data-toggle="tab", data-i18n="editor.level_tab_systems") Systems
         li
diff --git a/app/templates/modal/versions.jade b/app/templates/modal/versions.jade
index af78e7fcd..c3818573b 100755
--- a/app/templates/modal/versions.jade
+++ b/app/templates/modal/versions.jade
@@ -10,15 +10,21 @@ block modal-body-content
   if dataList
     table.table.table-condensed
       tr
+        th
         th(data-i18n="general.name") Name
         th(data-i18n="general.version") Version
         th(data-i18n="general.commit_msg") Commit Message
       for data in dataList
         tr
+          td
+            input(type="checkbox", value=data._id).select
           td
             a(href="/editor/#{page}/#{data.slug || data._id}")
               | #{data.name}
           td #{data.version.major}.#{data.version.minor}
           td #{data.commitMessage}
 
+  div.delta-container
+    div.delta-view
+
 block modal-footer-content
\ No newline at end of file
diff --git a/app/views/account/profile_view.coffee b/app/views/account/profile_view.coffee
index 5f38125f2..49249f317 100644
--- a/app/views/account/profile_view.coffee
+++ b/app/views/account/profile_view.coffee
@@ -18,7 +18,7 @@ module.exports = class ProfileView extends View
     super options
     if @userID is me.id
       @user = me
-    else
+    else if me.isAdmin() or "employer" in me.get('permissions')
       @user = User.getByID(@userID)
       @user.fetch()
       @listenTo @user, "sync", =>
@@ -27,6 +27,7 @@ module.exports = class ProfileView extends View
   getRenderData: ->
     context = super()
     context.user = @user
+    context.allowedToViewJobProfile = me.isAdmin() or "employer" in me.get('permissions')
     context.myProfile = @user.id is context.me.id
     context.marked = marked
     context.moment = moment
diff --git a/app/views/editor/article/edit.coffee b/app/views/editor/article/edit.coffee
index 0123546e0..c165c9701 100644
--- a/app/views/editor/article/edit.coffee
+++ b/app/views/editor/article/edit.coffee
@@ -49,7 +49,7 @@ module.exports = class ArticleEditView extends View
       data: data
       filePath: "db/thang.type/#{@article.get('original')}"
       schema: Article.schema
-      readOnly: true unless me.isAdmin() or @article.hasWriteAccess(me)
+      readOnly: me.get('anonymous')
       callbacks:
         change: @pushChangesToPreview
     @treema = @$el.find('#article-treema').treema(options)
diff --git a/app/views/editor/delta.coffee b/app/views/editor/delta.coffee
index b79b1cda5..9036f5557 100644
--- a/app/views/editor/delta.coffee
+++ b/app/views/editor/delta.coffee
@@ -9,19 +9,62 @@ TEXTDIFF_OPTIONS =
   viewType: 1
   
 module.exports = class DeltaView extends CocoView
+  
+  ###
+  Takes a CocoModel instance (model) and displays changes since the 
+  last save (attributes vs _revertAttributes).
+  
+  * If headModel is included, will look for and display conflicts with the changes in model.
+  * If comparisonModel is included, will show deltas between model and comparisonModel instead
+    of changes within model itself.
+    
+  ###
+  
   @deltaCounter: 0
   className: "delta-view"
   template: template
 
   constructor: (options) ->
     super(options)
-    @model = options.model
-    @headModel = options.headModel
-    @expandedDeltas = @model.getExpandedDelta()
+    @expandedDeltas = []
+    @skipPaths = options.skipPaths
+    
+    for modelName in ['model', 'headModel', 'comparisonModel']
+      continue unless m = options[modelName]
+      @[modelName] = @supermodel.loadModel(m, 'document').model
+    
+    @buildDeltas() if @supermodel.finished()
+    
+  onLoaded: ->
+    @buildDeltas()
+    super()
+    
+  buildDeltas: ->
+    if @comparisonModel
+      @expandedDeltas = @model.getExpandedDeltaWith(@comparisonModel)
+    else
+      @expandedDeltas = @model.getExpandedDelta()
+      
+    @filterExpandedDeltas()
+      
     if @headModel
       @headDeltas = @headModel.getExpandedDelta()
       @conflicts = deltasLib.getConflicts(@headDeltas, @expandedDeltas)
 
+  filterExpandedDeltas: ->
+    return unless @skipPaths
+    for path, i in @skipPaths
+      @skipPaths[i] = [path] if _.isString(path)
+    newDeltas = []
+    for delta in @expandedDeltas
+      skip = false
+      for skipPath in @skipPaths
+        if _.isEqual _.first(delta.dataPath, skipPath.length), skipPath
+          skip = true
+          break
+      newDeltas.push delta unless skip
+    @expandedDeltas = newDeltas
+
   getRenderData: ->
     c = super()
     c.deltas = @expandedDeltas
diff --git a/app/views/editor/level/component/edit.coffee b/app/views/editor/level/component/edit.coffee
index f5e8e3631..6d416e011 100644
--- a/app/views/editor/level/component/edit.coffee
+++ b/app/views/editor/level/component/edit.coffee
@@ -46,11 +46,12 @@ module.exports = class LevelComponentEditView extends View
     schema = _.cloneDeep LevelComponent.schema
     schema.properties = _.pick schema.properties, (value, key) => key in @editableSettings
     schema.required = _.intersection schema.required, @editableSettings
-    
+
     treemaOptions =
       supermodel: @supermodel
       schema: schema
       data: data
+      readonly: me.get('anonymous')
       callbacks: {change: @onComponentSettingsEdited}
     @componentSettingsTreema = @$el.find('#edit-component-treema').treema treemaOptions
     @componentSettingsTreema.build()
@@ -68,6 +69,7 @@ module.exports = class LevelComponentEditView extends View
       supermodel: @supermodel
       schema: LevelComponent.schema.properties.configSchema
       data: @levelComponent.get 'configSchema'
+      readOnly: me.get('anonymous')
       callbacks: {change: @onConfigSchemaEdited}
     @configSchemaTreema = @$el.find('#config-schema-treema').treema treemaOptions
     @configSchemaTreema.build()
@@ -84,13 +86,14 @@ module.exports = class LevelComponentEditView extends View
     editorEl = $('<div></div>').text(@levelComponent.get('code')).addClass('inner-editor')
     @$el.find('#component-code-editor').empty().append(editorEl)
     @editor = ace.edit(editorEl[0])
+    @editor.setReadOnly(me.get('anonymous'))
     session = @editor.getSession()
     session.setMode 'ace/mode/coffee'
     session.setTabSize 2
     session.setNewLineMode = 'unix'
     session.setUseSoftTabs true
     @editor.on('change', @onEditorChange)
-    
+
   onEditorChange: =>
     @levelComponent.set 'code', @editor.getValue()
     Backbone.Mediator.publish 'level-component-edited', levelComponent: @levelComponent
@@ -104,7 +107,7 @@ module.exports = class LevelComponentEditView extends View
     versionHistoryView = new VersionHistoryView {}, @levelComponent.id
     @openModalView versionHistoryView
     Backbone.Mediator.publish 'level:view-switched', e
-    
+
   startPatchingComponent: (e) ->
     @openModalView new SaveVersionModal({model:@levelComponent})
     Backbone.Mediator.publish 'level:view-switched', e
@@ -117,4 +120,3 @@ module.exports = class LevelComponentEditView extends View
   destroy: ->
     @editor?.destroy()
     super()
-
diff --git a/app/views/editor/level/components_tab_view.coffee b/app/views/editor/level/components_tab_view.coffee
index aadf30b1f..a0fe32f94 100644
--- a/app/views/editor/level/components_tab_view.coffee
+++ b/app/views/editor/level/components_tab_view.coffee
@@ -14,7 +14,6 @@ module.exports = class ComponentsTabView extends View
   className: 'tab-pane'
 
   subscriptions:
-    'level-thangs-changed': 'onLevelThangsChanged'
     'edit-level-component': 'editLevelComponent'
     'level-component-edited': 'onLevelComponentEdited'
     'level-component-editing-ended': 'onLevelComponentEditingEnded'
@@ -24,8 +23,8 @@ module.exports = class ComponentsTabView extends View
     'click #create-new-component-button-no-select': 'createNewLevelComponent'
 
   onLoaded: ->
-  onLevelThangsChanged: (e) ->
-    thangsData = e.thangsData
+
+  refreshLevelThangsTreema: (thangsData) ->
     presentComponents = {}
     for thang in thangsData
       for component in thang.components
diff --git a/app/views/editor/level/edit.coffee b/app/views/editor/level/edit.coffee
index 24dd13b10..c97be9459 100644
--- a/app/views/editor/level/edit.coffee
+++ b/app/views/editor/level/edit.coffee
@@ -29,6 +29,7 @@ module.exports = class EditorLevelView extends View
     'click #fork-level-start-button': 'startForkingLevel'
     'click #level-history-button': 'showVersionHistory'
     'click #patches-tab': -> @patchesView.load()
+    'click #components-tab': -> @componentsTab.refreshLevelThangsTreema @level.get('thangs')
     'click #level-patch-button': 'startPatchingLevel'
     'click #level-watch-button': 'toggleWatchLevel'
     'click #pop-level-i18n-button': -> @level.populateI18N()
diff --git a/app/views/editor/level/save_view.coffee b/app/views/editor/level/save_view.coffee
index 86ba7e96b..75226dd54 100644
--- a/app/views/editor/level/save_view.coffee
+++ b/app/views/editor/level/save_view.coffee
@@ -35,6 +35,7 @@ module.exports = class LevelSaveView extends SaveVersionModal
     models = if @lastContext.levelNeedsSave then [@level] else []
     models = models.concat @lastContext.modifiedComponents
     models = models.concat @lastContext.modifiedSystems
+    models = (m for m in models when m.hasWriteAccess())
     for changeEl, i in changeEls
       model = models[i]
       try
@@ -44,6 +45,7 @@ module.exports = class LevelSaveView extends SaveVersionModal
         console.error "Couldn't create delta view:", e
 
   shouldSaveEntity: (m) ->
+    return false unless m.hasWriteAccess()
     return true if m.hasLocalChanges()
     return true if (m.get('version').major is 0 and m.get('version').minor is 0) or not m.isPublished() and not m.collection
     # Sometimes we have two versions: one in a search collection and one with a URL. We only save changes to the latter.
diff --git a/app/views/editor/level/scripts_tab_view.coffee b/app/views/editor/level/scripts_tab_view.coffee
index 4c8f18912..b2789948f 100644
--- a/app/views/editor/level/scripts_tab_view.coffee
+++ b/app/views/editor/level/scripts_tab_view.coffee
@@ -58,7 +58,7 @@ module.exports = class ScriptsTabView extends View
       thangIDs: thangIDs
       dimensions: @dimensions
       supermodel: @supermodel
-      readOnly: true unless me.isAdmin() or @level.hasWriteAccess(me)
+      readOnly: me.get('anonymous')
       callbacks:
         change: @onScriptChanged
       nodeClasses:
diff --git a/app/views/editor/level/settings_tab_view.coffee b/app/views/editor/level/settings_tab_view.coffee
index 303fe7862..f834e172c 100644
--- a/app/views/editor/level/settings_tab_view.coffee
+++ b/app/views/editor/level/settings_tab_view.coffee
@@ -3,6 +3,7 @@ template = require 'templates/editor/level/settings_tab'
 Level = require 'models/Level'
 Surface = require 'lib/surface/Surface'
 nodes = require './treema_nodes'
+{me} = require 'lib/auth'
 
 module.exports = class SettingsTabView extends View
   id: 'editor-level-settings-tab-view'
@@ -34,7 +35,7 @@ module.exports = class SettingsTabView extends View
       supermodel: @supermodel
       schema: schema
       data: data
-      readOnly: true unless me.isAdmin() or @level.hasWriteAccess(me)
+      readOnly: me.get('anonymous')
       callbacks: {change: @onSettingsChanged}
       thangIDs: thangIDs
       nodeClasses:
diff --git a/app/views/editor/level/system/edit.coffee b/app/views/editor/level/system/edit.coffee
index a7699b316..404ac360b 100644
--- a/app/views/editor/level/system/edit.coffee
+++ b/app/views/editor/level/system/edit.coffee
@@ -49,7 +49,7 @@ module.exports = class LevelSystemEditView extends View
       schema: schema
       data: data
       callbacks: {change: @onSystemSettingsEdited}
-    treemaOptions.readOnly = true unless me.isAdmin()
+    treemaOptions.readOnly = me.get('anonymous')
     @systemSettingsTreema = @$el.find('#edit-system-treema').treema treemaOptions
     @systemSettingsTreema.build()
     @systemSettingsTreema.open()
@@ -67,7 +67,7 @@ module.exports = class LevelSystemEditView extends View
       schema: LevelSystem.schema.properties.configSchema
       data: @levelSystem.get 'configSchema'
       callbacks: {change: @onConfigSchemaEdited}
-    treemaOptions.readOnly = true unless me.isAdmin()
+    treemaOptions.readOnly = me.get('anonymous')
     @configSchemaTreema = @$el.find('#config-schema-treema').treema treemaOptions
     @configSchemaTreema.build()
     @configSchemaTreema.open()
@@ -83,7 +83,7 @@ module.exports = class LevelSystemEditView extends View
     editorEl = $('<div></div>').text(@levelSystem.get('code')).addClass('inner-editor')
     @$el.find('#system-code-editor').empty().append(editorEl)
     @editor = ace.edit(editorEl[0])
-    @editor.setReadOnly(not me.isAdmin())
+    @editor.setReadOnly(me.get('anonymous'))
     session = @editor.getSession()
     session.setMode 'ace/mode/coffee'
     session.setTabSize 2
diff --git a/app/views/editor/level/systems_tab_view.coffee b/app/views/editor/level/systems_tab_view.coffee
index 36b19e6e6..4d3ed88c6 100644
--- a/app/views/editor/level/systems_tab_view.coffee
+++ b/app/views/editor/level/systems_tab_view.coffee
@@ -53,7 +53,7 @@ module.exports = class SystemsTabView extends View
       supermodel: @supermodel
       schema: Level.schema.properties.systems
       data: systems
-      readOnly: true unless me.isAdmin() or @level.hasWriteAccess(me)
+      readOnly: me.get('anonymous')
       callbacks:
         change: @onSystemsChanged
         select: @onSystemSelected
diff --git a/app/views/editor/patch_modal.coffee b/app/views/editor/patch_modal.coffee
index e18fc1e9a..be8c4dde7 100644
--- a/app/views/editor/patch_modal.coffee
+++ b/app/views/editor/patch_modal.coffee
@@ -37,9 +37,11 @@ module.exports = class PatchModal extends ModalView
     if @targetModel.hasWriteAccess()
       headModel = @originalSource.clone(false)
       headModel.set(@targetModel.attributes)
+      headModel.loaded = true
     
     pendingModel = @originalSource.clone(false)
     pendingModel.applyDelta(@patch.get('delta'))
+    pendingModel.loaded = true
 
     @deltaView = new DeltaView({model:pendingModel, headModel:headModel})
     changeEl = @$el.find('.changes-stub')
diff --git a/app/views/editor/patches_view.coffee b/app/views/editor/patches_view.coffee
index 1c005df16..396106e86 100644
--- a/app/views/editor/patches_view.coffee
+++ b/app/views/editor/patches_view.coffee
@@ -22,7 +22,6 @@ module.exports = class PatchesView extends CocoView
     @patches = new PatchesCollection([], {}, @model, @status)
     
   load: ->
-    console.log 'load patches view?'
     @initPatches()
     @patches = @supermodel.loadCollection(@patches, 'patches').model
     @listenTo @patches, 'sync', @onPatchesLoaded
diff --git a/app/views/employers_view.coffee b/app/views/employers_view.coffee
index 44e3861c5..013519447 100644
--- a/app/views/employers_view.coffee
+++ b/app/views/employers_view.coffee
@@ -50,7 +50,9 @@ module.exports = class EmployersView extends View
   renderCandidatesAndSetupScrolling: =>
     @render()
     $(".nano").nanoScroller()
-    if window.location.hash.length is 25
+    if window.history?.state?.lastViewedCandidateID
+      $(".nano").nanoScroller({scrollTo:$("#" + window.history.state.lastViewedCandidateID)})
+    else if window.location.hash.length is 25
       $(".nano").nanoScroller({scrollTo:$(window.location.hash)})
 
   sortTable: ->
@@ -175,8 +177,13 @@ module.exports = class EmployersView extends View
 
   onCandidateClicked: (e) ->
     id = $(e.target).closest('tr').data('candidate-id')
-    window.location.hash = id
     if id
+      if window.history
+        oldState = _.cloneDeep window.history.state ? {}
+        oldState["lastViewedCandidateID"] = id
+        window.history.replaceState(oldState,"")
+      else  
+        window.location.hash = id
       url = "/account/profile/#{id}"
       app.router.navigate url, {trigger: true}
     else
diff --git a/app/views/kinds/RootView.coffee b/app/views/kinds/RootView.coffee
index 7ef3e7221..b67bdea31 100644
--- a/app/views/kinds/RootView.coffee
+++ b/app/views/kinds/RootView.coffee
@@ -38,9 +38,9 @@ module.exports = class RootView extends CocoView
     # force the browser to scroll to the hash
     # also messes with the browser history, so perhaps come up with a better solution
     super()
-    hash = location.hash
-    location.hash = ''
-    location.hash = hash
+    #hash = location.hash
+    #location.hash = ''
+    #location.hash = hash
     @renderScrollbar()
     #@$('.antiscroll-wrap').antiscroll()  # not yet, buggy
 
diff --git a/app/views/modal/employer_signup_modal.coffee b/app/views/modal/employer_signup_modal.coffee
index 96251ad3b..e7d88ac57 100644
--- a/app/views/modal/employer_signup_modal.coffee
+++ b/app/views/modal/employer_signup_modal.coffee
@@ -20,6 +20,7 @@ module.exports = class EmployerSignupView extends View
     "click #contract-agreement-button": "agreeToContract"
     "click #create-account-button": "createAccount"
     "click .login-link": "setHashToOpenModalAutomatically"
+    "keydown": "checkForFormSubmissionEnterPress"
 
 
   constructor: (options) ->
@@ -78,6 +79,9 @@ module.exports = class EmployerSignupView extends View
   handleAgreementFailure: (error) ->
     alert "There was an error signing the contract. Please contact team@codecombat.com with this error: #{error.responseText}"
 
+  checkForFormSubmissionEnterPress: (e) ->
+    if e.which is 13 then @createAccount(e)
+
   createAccount: (e) =>
     window.tracker?.trackEvent 'Finished Employer Signup'
     e.stopPropagation()
diff --git a/app/views/modal/versions_modal.coffee b/app/views/modal/versions_modal.coffee
index 6bf511396..9a00f5d58 100755
--- a/app/views/modal/versions_modal.coffee
+++ b/app/views/modal/versions_modal.coffee
@@ -1,6 +1,7 @@
 ModalView = require 'views/kinds/ModalView'
 template = require 'templates/modal/versions'
 tableTemplate = require 'templates/kinds/table'
+DeltaView = require 'views/editor/delta'
 
 class VersionsViewCollection extends Backbone.Collection
   url: ""
@@ -20,6 +21,9 @@ module.exports = class VersionsModalView extends ModalView
   id: ""
   url: ""
   page: ""
+  
+  events:
+    'change input.select': 'onSelectionChanged'
 
   constructor: (options, @ID, @model) ->
     super options
@@ -36,6 +40,18 @@ module.exports = class VersionsModalView extends ModalView
     @startsLoading = false
     @render()
 
+  onSelectionChanged: ->
+    rows = @$el.find 'input.select:checked'
+    deltaEl = @$el.find '.delta-view'
+    @removeSubView(@deltaView) if @deltaView
+    @deltaView = null
+    if rows.length isnt 2 then return 
+    
+    laterVersion = new @model(_id:$(rows[0]).val())
+    earlierVersion = new @model(_id:$(rows[1]).val())
+    @deltaView = new DeltaView({model:earlierVersion, comparisonModel:laterVersion, skipPaths:['_id','version', 'commitMessage', 'parent', 'created', 'slug', 'index']})
+    @insertSubView(@deltaView, deltaEl)
+
   getRenderData: (context={}) ->
     context = super(context)
     context.page = @page
diff --git a/app/views/play/ladder/simulate_tab.coffee b/app/views/play/ladder/simulate_tab.coffee
index 418838b43..645a4597a 100644
--- a/app/views/play/ladder/simulate_tab.coffee
+++ b/app/views/play/ladder/simulate_tab.coffee
@@ -38,7 +38,7 @@ module.exports = class SimulateTabView extends CocoView
   # Simulations
 
   onSimulateButtonClick: (e) ->
-    $("#simulate-button").prop "disabled",true
+    $("#simulate-button").prop "disabled", true
     $("#simulate-button").text "Simulating..."
 
     @simulator.fetchAndSimulateTask()
diff --git a/app/views/play/ladder_view.coffee b/app/views/play/ladder_view.coffee
index b82935ce4..2a65d44c2 100644
--- a/app/views/play/ladder_view.coffee
+++ b/app/views/play/ladder_view.coffee
@@ -38,7 +38,7 @@ module.exports = class LadderView extends RootView
     super(options)
     @level = @supermodel.loadModel(new Level(_id:@levelID), 'level').model
     @sessions = @supermodel.loadCollection(new LevelSessionsCollection(levelID), 'your_sessions').model
-    
+
     @teams = []
 
   onLoaded: ->
diff --git a/app/views/play/level/tome/problem.coffee b/app/views/play/level/tome/problem.coffee
index 2e1267785..9dc6db442 100644
--- a/app/views/play/level/tome/problem.coffee
+++ b/app/views/play/level/tome/problem.coffee
@@ -17,9 +17,9 @@ module.exports = class Problem
     @removeMarkerRange()
 
   buildAnnotation: ->
-    return unless @aetherProblem.ranges
+    return unless @aetherProblem.range
     text = @aetherProblem.message.replace /^Line \d+: /, ''
-    start = @aetherProblem.ranges[0][0]
+    start = @aetherProblem.range[0]
     @annotation =
       row: start.row,
       column: start.col,
@@ -33,8 +33,8 @@ module.exports = class Problem
     $(@ace.container).append @alertView.el
 
   buildMarkerRange: ->
-    return unless @aetherProblem.ranges
-    [start, end] = @aetherProblem.ranges[0]
+    return unless @aetherProblem.range
+    [start, end] = @aetherProblem.range
     clazz = "problem-marker-#{@aetherProblem.level}"
     @markerRange = new Range start.row, start.col, end.row, end.col
     @markerRange.start = @ace.getSession().getDocument().createAnchor @markerRange.start
diff --git a/app/views/play/level/tome/spell.coffee b/app/views/play/level/tome/spell.coffee
index c9d55953d..b547c854d 100644
--- a/app/views/play/level/tome/spell.coffee
+++ b/app/views/play/level/tome/spell.coffee
@@ -2,6 +2,9 @@ SpellView = require './spell_view'
 SpellListTabEntryView = require './spell_list_tab_entry_view'
 {me} = require 'lib/auth'
 
+Aether.addGlobal 'Vector', require 'lib/world/vector'
+Aether.addGlobal '_', _
+
 module.exports = class Spell
   loaded: false
   view: null
@@ -12,7 +15,6 @@ module.exports = class Spell
     @pathComponents = options.pathComponents
     @session = options.session
     @supermodel = options.supermodel
-    @skipFlow = options.skipFlow
     @skipProtectAPI = options.skipProtectAPI
     @worker = options.worker
     p = options.programmableMethod
@@ -106,21 +108,15 @@ module.exports = class Spell
         jshint_W091: {level: "ignore"}  # eliminates more hoisting problems
         jshint_E043: {level: "ignore"}  # https://github.com/codecombat/codecombat/issues/813 -- since we can't actually tell JSHint to really ignore things
         jshint_Unknown: {level: "ignore"}  # E043 also triggers Unknown, so ignore that, too
-        aether_MissingThis: {level: (if thang.requiresThis then 'error' else 'warning')}
+        aether_MissingThis: {level: 'error'}
       language: aceConfig.language ? 'javascript'
       functionName: @name
       functionParameters: @parameters
       yieldConditionally: thang.plan?
-      requiresThis: thang.requiresThis
+      globals: ['Vector', '_']
       # TODO: Gridmancer doesn't currently work with protectAPI, so hack it off
       protectAPI: not (@skipProtectAPI or window.currentView?.level.get('name').match("Gridmancer")) and @permissions.readwrite.length > 0  # If anyone can write to this method, we must protect it.
-      includeFlow: not @skipFlow and @canRead()
-        #callIndex: 0
-        #timelessVariables: ['i']
-        #statementIndex: 9001
-    if not (me.team in @permissions.readwrite) or window.currentView?.sessionID is "52bfb88099264e565d001349"  # temp fix for debugger explosion bug
-      #console.log "Turning off includeFlow for", @spellKey
-      aetherOptions.includeFlow = false
+      includeFlow: false
     #console.log "creating aether with options", aetherOptions
     aether = new Aether aetherOptions
     workerMessage =
diff --git a/app/views/play/level/tome/spell_debug_view.coffee b/app/views/play/level/tome/spell_debug_view.coffee
index dbf7a471b..43e6db210 100644
--- a/app/views/play/level/tome/spell_debug_view.coffee
+++ b/app/views/play/level/tome/spell_debug_view.coffee
@@ -13,6 +13,9 @@ module.exports = class DebugView extends View
 
   subscriptions:
     'god:new-world-created': 'onNewWorld'
+    'god:debug-value-return': 'handleDebugValue'
+    'tome:spell-shown': 'changeCurrentThangAndSpell'
+    'surface:frame-changed': 'onFrameChanged'
 
   events: {}
 
@@ -20,12 +23,25 @@ module.exports = class DebugView extends View
     super options
     @ace = options.ace
     @thang = options.thang
+    @spell = options.spell
     @variableStates = {}
     @globals = {Math: Math, _: _, String: String, Number: Number, Array: Array, Object: Object}  # ... add more as documented
-    for className, klass of serializedClasses
-      @globals[className] = klass
+    for className, serializedClass of serializedClasses
+      @globals[className] = serializedClass
+
     @onMouseMove = _.throttle @onMouseMove, 25
 
+  changeCurrentThangAndSpell: (thangAndSpellObject) ->
+    @thang = thangAndSpellObject.thang
+    @spell = thangAndSpellObject.spell
+
+  handleDebugValue: (returnObject) ->
+    {key, value} = returnObject
+    if @variableChain and not key is @variableChain.join(".") then return
+    @$el.find("code").text "#{key}: #{value}"
+    @$el.show().css(@pos)
+
+
   afterRender: ->
     super()
     @ace.on "mousemove", @onMouseMove
@@ -58,7 +74,8 @@ module.exports = class DebugView extends View
         token = prev
         start = it.getCurrentTokenColumn()
         chain.unshift token.value
-    if token and (token.value of @variableStates or token.value is "this" or @globals[token.value])
+    #Highlight all tokens, so true overrides all other conditions TODO: Refactor this later
+    if token and (true or token.value of @variableStates or token.value is "this" or @globals[token.value])
       @variableChain = chain
       offsetX = e.domEvent.offsetX ? e.clientX - $(e.domEvent.target).offset().left
       offsetY = e.domEvent.offsetY ? e.clientY - $(e.domEvent.target).offset().top
@@ -76,11 +93,18 @@ module.exports = class DebugView extends View
 
   onNewWorld: (e) ->
     @thang = @options.thang = e.world.thangMap[@thang.id] if @thang
-
+    
+  onFrameChanged: (data) ->
+    @currentFrame = data.frame
+    
   update: ->
     if @variableChain
-      {key, value} = @deserializeVariableChain @variableChain
-      @$el.find("code").text "#{key}: #{value}"
+      Backbone.Mediator.publish 'tome:spell-debug-value-request',
+        thangID: @thang.id
+        spellID: @spell.name
+        variableChain: @variableChain
+        frame: @currentFrame
+      @$el.find("code").text "Finding value..."
       @$el.show().css(@pos)
     else
       @$el.hide()
@@ -98,7 +122,6 @@ module.exports = class DebugView extends View
     @hoveredProperty = if @variableChain?.length is 2 then owner: @variableChain[0], property: @variableChain[1] else {}
     unless _.isEqual oldHoveredProperty, @hoveredProperty
       Backbone.Mediator.publish 'tome:spell-debug-property-hovered', @hoveredProperty
-
   updateMarker: ->
     if @marker
       @ace.getSession().removeMarker @marker
diff --git a/app/views/play/level/tome/spell_view.coffee b/app/views/play/level/tome/spell_view.coffee
index fb52061a2..9e1d578cb 100644
--- a/app/views/play/level/tome/spell_view.coffee
+++ b/app/views/play/level/tome/spell_view.coffee
@@ -193,7 +193,7 @@ module.exports = class SpellView extends View
     @createToolbarView()
 
   createDebugView: ->
-    @debugView = new SpellDebugView ace: @ace, thang: @thang
+    @debugView = new SpellDebugView ace: @ace, thang: @thang, spell:@spell
     @$el.append @debugView.render().$el.hide()
 
   createToolbarView: ->
@@ -422,6 +422,7 @@ module.exports = class SpellView extends View
     @spellHasChanged = false
 
   onUserCodeProblem: (e) ->
+    console.log "onUserCodeProblem", e, e.problem.userInfo.methodName is @spell.name, spellThang = _.find @spell.thangs, (spellThang, thangID) -> thangID is e.problem.userInfo.thangID
     return @onInfiniteLoop e if e.problem.id is "runtime_InfiniteLoop"
     return unless e.problem.userInfo.methodName is @spell.name
     return unless spellThang = _.find @spell.thangs, (spellThang, thangID) -> thangID is e.problem.userInfo.thangID
diff --git a/app/views/play/level/tome/tome_view.coffee b/app/views/play/level/tome/tome_view.coffee
index 77f1ab07a..972e925f0 100644
--- a/app/views/play/level/tome/tome_view.coffee
+++ b/app/views/play/level/tome/tome_view.coffee
@@ -120,8 +120,7 @@ module.exports = class TomeView extends View
         @thangSpells[thang.id].push spellKey
         unless method.cloneOf
           skipProtectAPI = @getQueryVariable "skip_protect_api", (@options.levelID in ['gridmancer'])
-          skipFlow = @getQueryVariable "skip_flow", (@options.levelID in ['brawlwood', 'greed', 'gold-rush'])
-          spell = @spells[spellKey] = new Spell programmableMethod: method, spellKey: spellKey, pathComponents: pathPrefixComponents.concat(pathComponents), session: @options.session, supermodel: @supermodel, skipFlow: skipFlow, skipProtectAPI: skipProtectAPI, worker: @worker
+          spell = @spells[spellKey] = new Spell programmableMethod: method, spellKey: spellKey, pathComponents: pathPrefixComponents.concat(pathComponents), session: @options.session, supermodel: @supermodel, skipProtectAPI: skipProtectAPI, worker: @worker
     for thangID, spellKeys of @thangSpells
       thang = world.getThangByID thangID
       if thang
@@ -142,15 +141,6 @@ module.exports = class TomeView extends View
     @cast()
 
   cast: ->
-    if @options.levelID is 'brawlwood'
-      # For performance reasons, only includeFlow on the currently Thang.
-      for spellKey, spell of @spells
-        for thangID, spellThang of spell.thangs
-          hadFlow = Boolean spellThang.aether.options.includeFlow
-          willHaveFlow = spellThang is @spellView?.spellThang
-          spellThang.aether.options.includeFlow = spellThang.aether.originalOptions.includeFlow = willHaveFlow
-          spellThang.aether.transpile spell.source unless hadFlow is willHaveFlow
-          #console.log "set includeFlow to", spellThang.aether.options.includeFlow, "for", thangID, "of", spellKey
     Backbone.Mediator.publish 'tome:cast-spells', spells: @spells
 
   onToggleSpellList: (e) ->
diff --git a/app/views/play/level_view.coffee b/app/views/play/level_view.coffee
index d1fef01f6..df66d44f6 100644
--- a/app/views/play/level_view.coffee
+++ b/app/views/play/level_view.coffee
@@ -12,7 +12,7 @@ Surface = require 'lib/surface/Surface'
 God = require 'lib/God'
 GoalManager = require 'lib/world/GoalManager'
 ScriptManager = require 'lib/scripts/ScriptManager'
-LevelBus = require('lib/LevelBus')
+LevelBus = require 'lib/LevelBus'
 LevelLoader = require 'lib/LevelLoader'
 LevelSession = require 'models/LevelSession'
 Level = require 'models/Level'
@@ -112,8 +112,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()
@@ -169,7 +169,7 @@ module.exports = class PlayLevelView extends View
     team = @getQueryVariable("team") ? @world.teamForPlayer(0)
     @loadOpponentTeam(team)
     @god.level = @level.serialize @supermodel
-    @god.worldClassMap = @world.classMap
+    @god.setWorldClassMap @world.classMap
     @setTeam team
     @initSurface()
     @initGoalManager()
@@ -427,7 +427,7 @@ module.exports = class PlayLevelView extends View
 
   initGoalManager: ->
     @goalManager = new GoalManager(@world, @level.get('goals'))
-    @god.goalManager = @goalManager
+    @god.setGoalManager @goalManager
 
   initScriptManager: ->
     @scriptManager = new ScriptManager({scripts: @world.scripts or [], view:@, session: @session})
diff --git a/app/views/play/spectate_view.coffee b/app/views/play/spectate_view.coffee
index 4a14f76c4..97bcc0a02 100644
--- a/app/views/play/spectate_view.coffee
+++ b/app/views/play/spectate_view.coffee
@@ -8,7 +8,7 @@ World = require 'lib/world/world'
 
 # tools
 Surface = require 'lib/surface/Surface'
-God = require 'lib/God'
+God = require 'lib/God' # 'lib/Buddha'
 GoalManager = require 'lib/world/GoalManager'
 ScriptManager = require 'lib/scripts/ScriptManager'
 LevelLoader = require 'lib/LevelLoader'
@@ -156,7 +156,7 @@ module.exports = class SpectateLevelView extends View
     team = @world.teamForPlayer(0)
     @loadOpponentTeam(team)
     @god.level = @level.serialize @supermodel
-    @god.worldClassMap = @world.classMap
+    @god.setWorldClassMap @world.classMap
     @setTeam team
     @initSurface()
     @initGoalManager()
@@ -387,7 +387,7 @@ module.exports = class SpectateLevelView extends View
 
   initGoalManager: ->
     @goalManager = new GoalManager(@world, @level.get('goals'))
-    @god.goalManager = @goalManager
+    @god.setGoalManager @goalManager
 
   initScriptManager: ->
     if @world.scripts
diff --git a/bower.json b/bower.json
index 7a25efe60..0c8b9ba8a 100644
--- a/bower.json
+++ b/bower.json
@@ -32,7 +32,7 @@
     "firepad": "~0.1.2",
     "marked": "~0.3.0",
     "moment": "~2.5.0",
-    "aether": "~0.1.18",
+    "aether": "~0.2.0",
     "underscore.string": "~2.3.3",
     "firebase": "~1.0.2",
     "catiline": "~2.9.3",
diff --git a/headless_client.coffee b/headless_client.coffee
new file mode 100644
index 000000000..01041a68a
--- /dev/null
+++ b/headless_client.coffee
@@ -0,0 +1,214 @@
+###
+This file will simulate games on node.js by emulating the browser environment.
+At some point, most of the code can be merged with Simulator.coffee
+###
+
+bowerComponentsPath = "./bower_components/"
+headlessClientPath = "./headless_client/"
+
+# SETTINGS
+options =
+  workerCode: require headlessClientPath + 'worker_world'
+  debug: false # Enable logging of ajax calls mainly
+  testing: true # Instead of simulating 'real' games, use the same one over and over again. Good for leak hunting.
+  testFile: require headlessClientPath + 'test.js'
+  leakTest: false # Install callback that tries to find leaks automatically
+  exitOnLeak: false # Exit if leak is found. Only useful if leaktest is set to true, obviously.
+  heapdump: false # Dumps the whole heap after every pass. The heap dumps can then be viewed in Chrome browser.
+  headlessClient: true
+
+options.heapdump = require('heapdump') if options.heapdump
+server = if options.testing then "http://127.0.0.1:3000" else "http://codecombat.com"
+
+# Disabled modules
+disable = [
+  'lib/AudioPlayer'
+  'locale/locale'
+  '../locale/locale'
+]
+
+
+# Start of the actual code. Setting up the enivronment to match the environment of the browser
+
+# the path used for the loader. __dirname is module dependent.
+path = __dirname
+
+m = require 'module'
+request = require 'request'
+
+originalLoader = m._load
+
+unhook = () ->
+  m._load = originalLoader
+
+hook = () ->
+  m._load = hookedLoader
+
+
+JASON = require 'jason'
+
+# Global emulated stuff
+GLOBAL.window = GLOBAL
+GLOBAL.document = location: pathname: "headless_client"
+GLOBAL.console.debug = console.log
+
+GLOBAL.Worker = require('webworker-threads').Worker
+Worker::removeEventListener = (what) ->
+  if what is 'message'
+    @onmessage = -> #This webworker api has only one event listener at a time.
+
+GLOBAL.tv4 = require('tv4').tv4
+
+GLOBAL.marked = setOptions: ->
+
+GLOBAL.navigator =
+#  userAgent: "nodejs"
+  platform: "headless_client"
+  vendor: "codecombat"
+  opera: false
+
+store = {}
+GLOBAL.localStorage =
+    getItem: (key) => store[key]
+    setItem: (key, s) => store[key] = s
+    removeItem: (key) => delete store[key]
+
+# Hook node.js require. See https://github.com/mfncooper/mockery/blob/master/mockery.js
+# The signature of this function *must* match that of Node's Module._load,
+# since it will replace that.
+# (Why is there no easier way?)
+hookedLoader = (request, parent, isMain) ->
+  if request == 'lib/God'
+    request = 'lib/Buddha'
+
+  if request in disable or ~request.indexOf('templates')
+    console.log 'Ignored ' + request if options.debug
+    return class fake
+  else if '/' in request and not (request[0] is '.') or request is 'application'
+    request = path + '/app/' + request
+  else if request is 'underscore'
+    request = 'lodash'
+
+  console.log "loading " + request if options.debug
+  originalLoader request, parent, isMain
+
+
+#jQuery wrapped for compatibility purposes. Poorly.
+GLOBAL.$ = GLOBAL.jQuery = (input) ->
+  console.log 'Ignored jQuery: ' + input if options.debug
+  append: (input)-> exports: ()->
+
+cookies = request.jar()
+
+$.ajax = (options) ->
+  responded = false
+  url = options.url
+  if url.indexOf('http')
+    url = '/' + url unless url[0] is '/'
+    url = server + url
+
+  data = options.data
+
+
+  #if (typeof data) is 'object'
+    #console.warn JSON.stringify data
+    #data = JSON.stringify data
+
+  console.log "Requesting: " + JSON.stringify options if options.debug
+  console.log "URL: " + url if options.debug
+  request
+    url: url
+    jar: cookies
+    json: options.parse
+    method: options.type
+    body: data
+    , (error, response, body) ->
+      console.log "HTTP Request:" + JSON.stringify options if options.debug and not error
+
+      if responded
+        console.log "\t↳Already returned before." if options.debug
+        return
+
+      if (error)
+        console.warn "\t↳Returned: error: #{error}"
+        options.error(error) if options.error?
+      else
+        console.log "\t↳Returned: statusCode #{response.statusCode}: #{if options.parse then JSON.stringify body else body}" if options.debug
+        options.success(body, response, status: response.statusCode) if options.success?
+
+
+      statusCode = response.statusCode if response?
+      options.complete(status: statusCode) if options.complete?
+      responded = true
+
+$.extend = (deep, into, from) ->
+  copy = _.clone(from, deep);
+  if into
+    _.assign into, copy
+    copy = into
+  copy
+
+$.isArray = (object) ->
+  _.isArray object
+
+$.isPlainObject = (object) ->
+  _.isPlainObject object
+
+
+do (setupLodash = this) ->
+  GLOBAL._ = require 'lodash'
+  _.str = require 'underscore.string'
+  _.string = _.str
+  _.mixin _.str.exports()
+
+
+# load Backbone. Needs hooked loader to reroute underscore to lodash.
+hook()
+GLOBAL.Backbone = require bowerComponentsPath + 'backbone/backbone'
+unhook()
+Backbone.$ = $
+
+require bowerComponentsPath + 'validated-backbone-mediator/backbone-mediator'
+# Instead of mediator, dummy might be faster yet suffice?
+#Mediator = class Mediator
+#  publish: (id, object) ->
+#    console.Log "Published #{id}: #{object}"
+#  @subscribe: () ->
+#  @unsubscribe: () ->
+
+GLOBAL.Aether = require 'aether'
+
+# Set up new loader.
+hook()
+
+login = require './login.coffee' #should contain an object containing they keys 'username' and 'password'
+
+
+#Login user and start the code.
+$.ajax
+  url: '/auth/login'
+  type: "POST"
+  data: login
+  parse: true
+  error: (error) -> "Bad Error. Can't connect to server or something. " + error
+  success: (response) ->
+    console.log "User: " + response
+    GLOBAL.window.userObject = response # JSON.parse response
+
+    User = require 'models/User'
+
+    World = require 'lib/world/world'
+    LevelLoader = require 'lib/LevelLoader'
+    GoalManager = require 'lib/world/GoalManager'
+
+    SuperModel = require 'models/SuperModel'
+
+    log = require 'winston'
+
+    CocoClass = require 'lib/CocoClass'
+
+    Simulator = require 'lib/simulator/Simulator'
+
+    sim = new Simulator options
+
+    sim.fetchAndSimulateTask()
diff --git a/headless_client/test.js b/headless_client/test.js
new file mode 100644
index 000000000..fdd51bf83
--- /dev/null
+++ b/headless_client/test.js
@@ -0,0 +1,87 @@
+module.exports = {
+    "messageGenerated": 1396792689279,
+    "sessions": [
+    {
+        "sessionID": "533a2c4893b95d9319a58049",
+        "submitDate": "2014-04-06T06:31:11.806Z",
+        "team": "humans",
+        "code": {
+            "ogre-base": {
+                "chooseAction": "// This is the code for your base. Decide which unit to build each frame.\n// Units you build will go into the this.built array.\n// Destroy the enemy base within 60 seconds!\n// Check out the Guide at the top for more info.\n\n// Choose your hero! You can only build one hero.\nvar hero;\n//hero = 'ironjaw';  // A leaping juggernaut hero, type 'brawler'.\nhero = 'yugargen';  // A devious spellcaster hero, type 'shaman'.\nif(hero && !this.builtHero) {\n    this.builtHero = this.build(hero);\n    return;\n}\n\n// Munchkins are weak melee units with 1.25s build cooldown.\n// Throwers are fragile, deadly ranged units with 2.5s build cooldown.\nvar buildOrder = ['munchkin', 'thrower', 'munchkin', 'thrower', 'munchkin', 'thrower'];\nvar type = buildOrder[this.built.length % buildOrder.length];\n//this.say('Unit #' + this.built.length + ' will be a ' + type);\nthis.build(type);\n//this.say(\"Move\", {to:{x:20, y:30}});{x: 68, y: 29}{x: 70, y: 30}"
+            },
+            "programmable-shaman": {
+                "chooseAction": "if (this.hero !== undefined) {\n    this.hero = this.getNearest(enemy);\n}\n// Shamans are spellcasters with a weak magic attack\n// and three spells: 'shrink', 'grow', and 'poison-cloud'.\n// Shrink: target has 2/3 health, 1.5x speed for 5s.\n// Grow: target has double health, half speed for 5s.\n// Once per match, she can cast poison cloud, which does\n// 5 poison dps for 10s to enemies in a 10m radius.\nvar right = 0;\nif(right === 0){this.move({x: 70, y: 40});\n}\nvar friends = this.getFriends();\nvar enemies = this.getEnemies();\nif (enemies.length === 0) return;  // Chill if all enemies are dead.\nvar enemy = this.getNearest(enemies);\nvar friend = this.getNearest(friends);\n\nif(this.canCast('shrink', enemy)) \n{\n    this.castShrink(enemy);\n}\nelse\n{\n    this.castGrow(friend);\n}\n\nvar enemiesinpoisonrange = 0;\nfor (var i = 0; i < enemies.lenght; ++i) {\n    var enemi = enemies[i];\n    if (this.distance(enemi) <= 10) {\n        enemiesinpoisonrange++;\n    }\n}\nif (enemiesinpoisonrange >= 7) {\n    this.castPoisonCloud(enemy);\n}\n//if (this.distance(ogrebase) > 10) {\n//    this.move({x: 70, y: 30});\n//}\n//this.say(\"Defend!\", {targetPos: {x: 45, y: 30}});\n\n//this.say(\"Defend!\", {targetPos: {x: 35, y: 30}});\n\n//this.say(\"Defend!\", {targetPos: {x: 25, y: 30}});\n\n//this.say(\"Attack!\", {to:{x:20, y:30}});\n\n\n// Which one do you do at any given time? Only the last called action happens.\n//if(this.canCast('shrink', enemy)) this.castShrink(enemy);\n//if(this.canCast('grow', friend)) this.castGrow(friend);\n//if(this.canCast('poison-cloud', enemy)) this.castPoisonCloud(enemy);\n//this.attack(enemy);\n\n// You can also command your troops with this.say():\n//this.say(\"Defend!\", {targetPos: {x: 45, y: 30}});\n//this.say(\"Attack!\", {target: enemy});\n//this.say(\"Move!\", {targetPos: {x: 50, y: 40});"
+            },
+            "programmable-brawler": {
+                "chooseAction": "// The Brawler is a huge melee hero with mighty mass.\n// this.throw() hurls an enemy behind him.\n// this.jumpTo() leaps to a target within 20m every 10s.\n// this.stomp() knocks everyone away, once per match.\n\nvar friends = this.getFriends();\nvar enemies = this.getEnemies();\nif (enemies.length === 0) return;  // Chill if all enemies are dead.\nvar enemy = this.getNearest(enemies);\nvar friend = this.getNearest(friends);\n\n// Which one do you do at any given time? Only the last called action happens.\n//if(!this.getCooldown('jump')) this.jumpTo(enemy.pos);\n//if(!this.getCooldown('stomp') && this.distance(enemy) < 10) this.stomp();\n//if(!this.getCooldown('throw')) this.throw(enemy);\n//this.attack(enemy);\n\n// You can also command your troops with this.say():\n//this.say(\"Defend!\", {targetPos: {x: 60, y: 30}}));\n//this.say(\"Attack!\", {target: enemy});\n//this.say(\"Move!\", {targetPos: {x: 50, y: 40});\n\n// You can store state on this across frames:\n//this.lastHealth = this.health;{x: 68, y: 29}{x: 70, y: 30}"
+            },
+            "programmable-librarian": {
+                "chooseAction": "var enemies = this.getEnemies();\nif (enemies.length === 0)\n    return;\nvar enemy = this.getNearest(enemies);\nvar friends = this.getFriends();\nvar friend = this.getNearest(friends);\nvar archer = this.getFriends(type, \"archer\");\nvar soldier = this.getFriends(type, \"soldier\");\nvar hero = this.getFriends(type, \"hushbaum\");\nvar rand = Math.random();\nvar xmove;\nvar ymove;\nfor (var i = 0; i < enemies.length / 3; i += 1) {\n    var e = enemies[i];\n    var ehealth = Math.floor(e.health);\n    if (this.canCast(\"haste\", friend)) {\n        this.say(\"Godspeed \" + friend.id + \"!\");\n        this.castHaste(friend);\n    }\n    if (this.canCast(\"haste\", this)) {\n        this.say(\"I am Godspeed!\");\n        this.castHaste(this);\n    }\n    if (this.canCast(\"slow\", e)) {\n        this.say(\"Chill Out \" + e.id + \"!\");\n        this.castSlow(e);\n    }\n    if (this.distance(e) < 45) {\n        this.attack(e);\n        this.say(\"Attacking \" + e.id + \" life is \" + ehealth + \".\");\n    }\n    if (this.health < this.maxHealth * 0.75) {\n        if (this.pos.x > 20) {\n            this.move({\n                x: this.pos.x - 20,\n                y: this.pos.y\n            });\n        } else {\n            this.move({\n                x: this.pos.x + 20,\n                y: this.pos.y\n            });\n        }\n    }\n    if (this.canCast(\"regen\", this)) {\n        this.castRegen(this);\n        this.say(\"I won't die today bitch!\");\n    }\n    if (friend.health < friend.maxHealth * 0.5) {\n        if (this.canCast(\"regen\", friend)) {\n            this.say(\"You won't die today \" + friend.id + \".\");\n            this.castRegen(friend);\n        }\n    }\n}\n;"
+            },
+            "programmable-tharin": {
+                "chooseAction": "// Tharin is a melee fighter with shield, warcry, and terrify skills.\n// this.shield() lets him take one-third damage while defending.\n// this.warcry() gives allies within 10m 30% haste for 5s, every 10s.\n// this.terrify() sends foes within 30m fleeing for 5s, once per match.\n\nvar friends = this.getFriends();\nvar enemies = this.getEnemies();\nif (enemies.length === 0) return;  // Chill if all enemies are dead.\nvar enemy = this.getNearest(enemies);\nvar friend = this.getNearest(friends);\n\n// Which one do you do at any given time? Only the last called action happens.\n//if(!this.getCooldown('warcry')) this.warcry();\n//if(!this.getCooldown('terrify')) this.terrify();\n//this.shield();\n//this.attack(enemy);\n\n// You can also command your troops with this.say():\n//this.say(\"Defend!\", {targetPos: {x: 30, y: 30}}));\n//this.say(\"Attack!\", {target: enemy});\n//this.say(\"Move!\", {targetPos: {x: 40, y: 40});\n\n// You can store state on this across frames:\n//this.lastHealth = this.health;"
+            },
+            "human-base": {
+                "chooseAction": "// This is the code for your base. Decide which unit to build each frame.\n// Units you build will go into the this.built array.\n// Destroy the enemy base within 60 seconds!\n// Check out the Guide at the top for more info.\n\n// CHOOSE YOUR HERO! You can only build one hero.\nvar hero;\n//hero = 'tharin';  // A fierce knight with battlecry abilities.\nhero = 'hushbaum';  // A fiery spellcaster hero.\n\nif(hero && !this.builtHero) {\n    this.builtHero = this.build(hero);\n    return;\n}\n\n// Soldiers are hard-to-kill, low damage melee units with 2s build cooldown.\n// Archers are fragile but deadly ranged units with 2.5s build cooldown.\nvar buildOrder = ['soldier', 'soldier', 'archer', 'archer', 'soldier', 'soldier'];\nvar type = buildOrder[this.built.length % buildOrder.length];\nthis.say('Unit #' + this.built.length + ' will be a ' + type);\nthis.build(type);\n\n "
+            }
+        },
+        "teamSpells": {
+            "ogres": [
+                "programmable-brawler/chooseAction",
+                "programmable-shaman/chooseAction",
+                "ogre-base/chooseAction"
+            ],
+            "humans": [
+                "programmable-librarian/chooseAction",
+                "programmable-tharin/chooseAction",
+                "human-base/chooseAction"
+            ]
+        },
+        "levelID": "dungeon-arena",
+        "creator": "5338c38c4811eff221de2347",
+        "creatorName": "iC0DE"
+    },
+    {
+        "sessionID": "532a777c2042708b711a6c29",
+        "submitDate": "2014-03-20T05:45:54.691Z",
+        "team": "ogres",
+        "code": {
+            "ogre-base": {
+                "chooseAction": "// This is the code for your base. Decide which unit to build each frame.\n// Units you build will go into the this.built array.\n// Destroy the enemy base within 60 seconds!\n// Check out the Guide at the top for more info.\n\n// Choose your hero! You can only build one hero.\nvar hero;\n//hero = 'ironjaw';  // A leaping juggernaut hero, type 'brawler'.\nhero = 'yugargen';  // A devious spellcaster hero, type 'shaman'.\nif(hero && !this.builtHero) {\n    this.builtHero = this.build(hero);\n    return;\n}\n\n// Munchkins are weak melee units with 1.25s build cooldown.\n// Throwers are fragile, deadly ranged units with 2.5s build cooldown.\nvar buildOrder = ['munchkin', 'munchkin', 'munchkin', 'thrower'];\nvar type = buildOrder[this.built.length % buildOrder.length];\n//this.say('Unit #' + this.built.length + ' will be a ' + type);\nthis.build(type);"
+            },
+            "programmable-shaman": {
+                "chooseAction": "// Shamans are spellcasters with a weak magic attack\n// and three spells: 'shrink', 'grow', and 'poison-cloud'.\n// Shrink: target has 2/3 health, 1.5x speed for 5s.\n// Grow: target has double health, half speed for 5s.\n// Once per match, she can cast poison cloud, which does\n// 5 poison dps for 10s to enemies in a 10m radius.\n\nvar friends = this.getFriends();\nvar enemies = this.getEnemies();\nif (enemies.length === 0) {\n    return;  // Chill if all enemies are dead.\n}\nvar enemy = this.getNearest(enemies);\nvar friend = this.getNearest(friends);\nif (enemies.length > 5) {\n    if(this.canCast('poison-cloud', enemy)) {\n        this.castPoisonCloud(enemy);\n        return;\n    }\n}\n\nif (friends.length > 4) {\n    this.attack(enemy); \n}\nfor (var i = 0; i < friends.length; ++i) {\n    if (friends[i].health < 0) {\n        continue;\n    }\n    if(friends[i].type == \"thrower\" && this.canCast('shrink', friends[i])) {\n        this.castShrink(friends[i]);\n        return;\n    } \n    if(friends[i].type == \"munchkin\" && this.canCast('grow', friends[i])) {\n        this.castGrow(friends[i]);\n        return;\n    } \n}\n\n// Which one do you do at any given time? Only the last called action happens.\n//if(this.canCast('shrink', enemy)) this.castShrink(enemy);\n//if(this.canCast('grow', friend)) this.castGrow(friend);\n//if(this.canCast('poison-cloud', enemy)) this.castPoisonCloud(enemy);\n//this.attack(enemy);\n\n// You can also command your troops with this.say():\n//this.say(\"Defend!\", {targetPos: {x: 60, y: 30}}));\n//this.say(\"Attack!\", {target: enemy});\n//this.say(\"Move!\", {targetPos: {x: 50, y: 40});"
+            },
+            "programmable-brawler": {
+                "chooseAction": "// The Brawler is a huge melee hero with mighty mass.\n// this.throw() hurls an enemy behind him.\n// this.jumpTo() leaps to a target within 20m every 10s.\n// this.stomp() knocks everyone away, once per match.\n\nvar friends = this.getFriends();\nvar enemies = this.getEnemies();\nif (enemies.length === 0) return;  // Chill if all enemies are dead.\nvar enemy = this.getNearest(enemies);\nvar friend = this.getNearest(friends);\n\n// Which one do you do at any given time? Only the last called action happens.\n//if(!this.getCooldown('jump')) this.jumpTo(enemy.pos);\n//if(!this.getCooldown('stomp') && this.distance(enemy) < 10) this.stomp();\n//if(!this.getCooldown('throw')) this.throw(enemy);\n//this.attack(enemy);\n\n// You can also command your troops with this.say():\n//this.say(\"Defend!\", {targetPos: {x: 60, y: 30}}));\n//this.say(\"Attack!\", {target: enemy});\n//this.say(\"Move!\", {targetPos: {x: 50, y: 40});\n\n// You can store state on this across frames:\n//this.lastHealth = this.health;"
+            },
+            "human-base": {
+                "chooseAction": "// This is the code for your base. Decide which unit to build each frame.\n// Units you build will go into the this.built array.\n// Destroy the enemy base within 60 seconds!\n// Check out the Guide at the top for more info.\n\n// CHOOSE YOUR HERO! You can only build one hero.\nvar hero;\nhero = 'tharin';  // A fierce knight with battlecry abilities.\n//hero = 'hushbaum';  // A fiery spellcaster hero.\n\nif(hero && !this.builtHero) {\n    this.builtHero = this.build(hero);\n    return;\n}\n\n// Soldiers are hard-to-kill, low damage melee units with 2s build cooldown.\n// Archers are fragile but deadly ranged units with 2.5s build cooldown.\nvar buildOrder = ['archer', 'archer', 'soldier', 'archer', 'soldier'];\nvar type = buildOrder[this.built.length % buildOrder.length];\n//this.say('Unit #' + this.built.length + ' will be a ' + type);\nthis.build(type);"
+            },
+            "programmable-tharin": {
+                "chooseAction": "this.findTypeInRange = function(units, type) {\n    for (var i = 0; i < units.length; ++i) {\n        var unit = units[i];\n        if (unit.type === type && this.distance(unit) < 20)\n            return unit;\n    }\n    return null;\n};\n\nthis.findType = function(units, type) {\n    for (var i = 0; i < units.length; ++i) {\n        var unit = units[i];\n        if (unit.type === type)\n            return unit;\n    }\n    return null;\n};\n\nthis.findHeroInRange = function(units, range) {\n    for (var i = 0; i < units.length; ++i) {\n        var unit = units[i];\n        if ((unit.type === 'shaman' || unit.type === 'brawler') && this.distance(unit) < range)\n            return unit;\n    }\n    return null;\n};\n\n// Tharin is a melee fighter with shield, warcry, and terrify skills.\n// this.shield() lets him take one-third damage while defending.\n// this.warcry() gives allies within 10m 30% haste for 5s, every 10s.\n// this.terrify() sends foes within 30m fleeing for 5s, once per match.\n\nvar friends = this.getFriends();\nvar enemies = this.getEnemies();\n\n//Enemies\nvar enemyBase = this.findType(enemies, 'base');\nvar brawler = this.findTypeInRange(enemies, 'brawler');\nvar shaman = this.findTypeInRange(enemies, 'shaman');\n\nif (enemies.length === 0) return;  // Chill if all enemies are dead.\nvar enemy = this.getNearest(enemies);\nvar friend = this.getNearest(friends);\n\n// Which one do you do at any given time? Only the last called action happens.\n//if(!this.getCooldown('warcry')) this.warcry();\n//if(!this.getCooldown('terrify')) this.terrify();\n//this.shield();\n\nif((brawler || shaman) && !this.attackTime)\n{\n    this.attackTime = true;\n    if(brawler)\n        this.say(\"Attack!\", {target: brawler});\n    else if(shaman)\n        this.say(\"Attack!\", {target: shaman});\n}\nelse if(this.health < 15 && this.getCooldown('terrify'))\n{\n    this.terrify();\n}\nelse if(this.findHeroInRange(enemies, 30) && this.getCooldown('terrify'))\n{\n    this.terrify();\n}\nelse if(this.health < 25)\n{\n    this.shield();\n}\nelse if(brawler && this.distance(brawler) <=10)\n{\n    this.attack(brawler);\n}\nelse\n{\n    this.attack(enemy);\n}\n\n// You can also command your troops with this.say():\n//this.say(\"Defend!\", {targetPos: {x: 30, y: 30}}));\n//this.say(\"Attack!\", {target: enemy});\n//this.say(\"Move!\", {targetPos: {x: 40, y: 40});\n\n// You can store state on this across frames:\n//this.lastHealth = this.health;"
+            },
+            "programmable-librarian": {
+                "chooseAction": "// The Librarian is a spellcaster with a fireball attack\n// plus three useful spells: 'slow', 'regen', and 'haste'.\n// Slow makes a target move and attack at half speed for 5s.\n// Regen makes a target heal 10 hp/s for 10s.\n// Haste speeds up a target by 4x for 5s, once per match.\n\nvar friends = this.getFriends();\nvar enemies = this.getEnemies();\nif (enemies.length === 0) return;  // Chill if all enemies are dead.\nvar enemy = this.getNearest(enemies);\nvar friend = this.getNearest(friends);\n\n// Which one do you do at any given time? Only the last called action happens.\n//if(this.canCast('slow', enemy)) this.castSlow(enemy);\n//if(this.canCast('regen', friend)) this.castRegen(friend);\n//if(this.canCast('haste', friend)) this.castHaste(friend);\n//this.attack(enemy);\n\n// You can also command your troops with this.say():\n//this.say(\"Defend!\", {targetPos: {x: 30, y: 30}}));\n//this.say(\"Attack!\", {target: enemy});\n//this.say(\"Move!\", {targetPos: {x: 50, y: 40});"
+            }
+        },
+        "teamSpells": {
+            "ogres": [
+                "programmable-brawler/chooseAction",
+                "programmable-shaman/chooseAction",
+                "ogre-base/chooseAction"
+            ],
+            "humans": [
+                "programmable-librarian/chooseAction",
+                "programmable-tharin/chooseAction",
+                "human-base/chooseAction"
+            ]
+        },
+        "levelID": "dungeon-arena",
+        "creator": "53291a80b112e7240f324667",
+        "creatorName": "Imbal Oceanrage"
+    }
+],
+    "taskID": "53415d71942d00aa43dbf3e9",
+    "receiptHandle": "cd50e44db7dbd4cc0bcce047aa822ba2fe3556cf"
+}
\ No newline at end of file
diff --git a/headless_client/worker_world.coffee b/headless_client/worker_world.coffee
new file mode 100644
index 000000000..c4ccf32d8
--- /dev/null
+++ b/headless_client/worker_world.coffee
@@ -0,0 +1,209 @@
+# function to use inside a webworker.
+# This function needs to run inside an environment that has a 'self'.
+# This specific worker is targeted towards the node.js headless_client environment.
+
+JASON = require 'jason'
+fs = require 'fs'
+
+betterConsole = () ->
+
+  self.logLimit = 200;
+  self.logsLogged = 0;
+
+  self.transferableSupported = () -> true
+
+  self.console = log: ->
+    if self.logsLogged++ is self.logLimit
+      self.postMessage
+        type: "console-log"
+        args: ["Log limit " + self.logLimit + " reached; shutting up."]
+        id: self.workerID
+
+    else if self.logsLogged < self.logLimit
+      args = [].slice.call(arguments)
+      i = 0
+
+      while i < args.length
+        args[i] = args[i].toString()  if args[i].constructor.className is "Thang" or args[i].isComponent  if args[i] and args[i].constructor
+        ++i
+      try
+        self.postMessage
+          type: "console-log"
+          args: args
+          id: self.workerID
+
+      catch error
+        self.postMessage
+          type: "console-log"
+          args: [
+              "Could not post log: " + args
+              error.toString()
+              error.stack
+              error.stackTrace
+          ]
+          id: self.workerID
+
+  # so that we don't crash when debugging statements happen
+  self.console.error = self.console.info = self.console.log
+  GLOBAL.console = console = self.console
+  self.console
+
+
+work = () ->
+  console.log "starting..."
+
+  console.log = ->
+
+  World = self.require('lib/world/world');
+  GoalManager = self.require('lib/world/GoalManager');
+
+  self.cleanUp = ->
+    self.world = null
+    self.goalManager = null
+    self.postedErrors = {}
+    self.t0 = null
+    self.logsLogged = 0
+
+  self.runWorld = (args) ->
+    console.log "Running world inside worker."
+    self.postedErrors = {}
+    self.t0 = new Date()
+    self.postedErrors = false
+    self.logsLogged = 0
+
+    try
+      self.world = new World(args.worldName, args.userCodeMap)
+      self.world.loadFromLevel args.level, true  if args.level
+      self.goalManager = new GoalManager(self.world)
+      self.goalManager.setGoals args.goals
+      self.goalManager.setCode args.userCodeMap
+      self.goalManager.worldGenerationWillBegin()
+      self.world.setGoalManager self.goalManager
+    catch error
+      console.log "There has been an error inside the worker."
+      self.onWorldError error
+      return
+    Math.random = self.world.rand.randf # so user code is predictable
+    console.log "Loading frames."
+
+    self.postMessage type: "start-load-frames"
+
+
+    self.world.loadFrames self.onWorldLoaded, self.onWorldError, self.onWorldLoadProgress, true
+
+
+  self.onWorldLoaded = onWorldLoaded = ->
+    self.postMessage type: "end-load-frames"
+
+    self.goalManager.worldGenerationEnded()
+    t1 = new Date()
+    diff = t1 - self.t0
+    transferableSupported = self.transferableSupported()
+    try
+      serialized = serializedWorld: undefined # self.world.serialize()
+      transferableSupported = false
+    catch error
+      console.log "World serialization error:", error.toString() + "\n" + error.stack or error.stackTrace
+    t2 = new Date()
+
+    # console.log("About to transfer", serialized.serializedWorld.trackedPropertiesPerThangValues, serialized.transferableObjects);
+    try
+      if transferableSupported
+        self.postMessage
+          type: "new-world"
+          serialized: serialized.serializedWorld
+          goalStates: self.goalManager.getGoalStates()
+        , serialized.transferableObjects
+      else
+        self.postMessage
+          type: "new-world"
+          serialized: serialized.serializedWorld
+          goalStates: self.goalManager.getGoalStates()
+
+    catch error
+      console.log "World delivery error:", error.toString() + "\n" + error.stack or error.stackTrace
+    t3 = new Date()
+    console.log "And it was so: (" + (diff / self.world.totalFrames).toFixed(3) + "ms per frame,", self.world.totalFrames, "frames)\nSimulation   :", diff + "ms \nSerialization:", (t2 - t1) + "ms\nDelivery     :", (t3 - t2) + "ms"
+    self.cleanUp()
+
+
+  self.onWorldError = onWorldError = (error) ->
+    self.postMessage type: "end-load-frames"
+    if error instanceof Aether.problems.UserCodeProblem
+      #console.log "Aether userCodeProblem occured."
+      unless self.postedErrors[error.key]
+        problem = error.serialize()
+        self.postMessage
+          type: "user-code-problem"
+          problem: problem
+
+        self.postedErrors[error.key] = problem
+    else
+      console.log "Non-UserCodeError:", error.toString() + "\n" + error.stack or error.stackTrace
+    self.cleanUp()
+
+  self.onWorldLoadProgress = onWorldLoadProgress = (progress) ->
+    #console.log "Worker onWorldLoadProgress"
+    self.postMessage
+      type: "world-load-progress-changed"
+      progress: progress
+
+  self.abort = abort = ->
+    #console.log "Abort called for worker."
+    if self.world and self.world.name
+      #console.log "About to abort:", self.world.name, typeof self.world.abort
+      self.world.abort()  if typeof self.world isnt "undefined"
+      self.world = null
+    self.postMessage type: "abort"
+    self.cleanUp()
+
+  self.reportIn = reportIn = ->
+    console.log "Reporting in."
+    self.postMessage type: "reportIn"
+
+  self.addEventListener "message", (event) ->
+    #console.log JSON.stringify event
+    self[event.data.func] event.data.args
+
+  self.postMessage type: "worker-initialized"
+
+world = fs.readFileSync "./public/javascripts/world.js", 'utf8'
+
+
+#window.BOX2D_ENABLED = true;
+
+newConsole = "newConsole = #{}JASON.stringify newConsole}()";
+
+ret = """
+
+  GLOBAL = root = window = self;
+  GLOBAL.window = window;
+
+  self.workerID = "Worker";
+
+  console = #{JASON.stringify betterConsole}();
+
+  try {
+    // the world javascript file
+    #{world};
+
+    // Don't let user generated code access stuff from our file system!
+    self.importScripts = importScripts = null;
+    self.native_fs_ = native_fs_ = null;
+
+    // the actual function
+    #{JASON.stringify work}();
+  }catch (error) {
+    self.postMessage({"type": "console-log", args: ["An unhandled error occured: ", error.toString(), error.stack], id: -1});
+  }
+"""
+
+
+#console = #{JASON.stringify createConsole}();
+#
+#  console.error = console.info = console.log;
+#self.console = console;
+#GLOBAL.console = console;
+
+
+module.exports = new Function(ret)
diff --git a/package.json b/package.json
index 10c966fd8..268232a83 100644
--- a/package.json
+++ b/package.json
@@ -60,9 +60,13 @@
     "gridfs-stream": "0.4.x",
     "stream-buffers": "0.2.x",
     "sendwithus": "2.0.x",
-    "aws-sdk":"~2.0.0",
-    "bayesian-battle":"0.0.x",
-    "redis": ""
+    "aws-sdk": "~2.0.0",
+    "bayesian-battle": "0.0.x",
+    "redis": "",
+    "webworker-threads": "~0.4.11",
+    "node-gyp": "~0.13.0",
+    "aether": "~0.2.0",
+    "JASON": "~0.1.3"
   },
   "devDependencies": {
     "jade": "0.33.x",
diff --git a/scripts/mongodb/migrations/2014-02-22-migrate-emails.js b/scripts/mongodb/migrations/2014-04-22-migrate-emails.js
similarity index 100%
rename from scripts/mongodb/migrations/2014-02-22-migrate-emails.js
rename to scripts/mongodb/migrations/2014-04-22-migrate-emails.js
diff --git a/scripts/mongodb/migrations/2014-05-08-populate-watchers.js b/scripts/mongodb/migrations/2014-05-08-populate-watchers.js
new file mode 100644
index 000000000..b6a56b475
--- /dev/null
+++ b/scripts/mongodb/migrations/2014-05-08-populate-watchers.js
@@ -0,0 +1,70 @@
+var scott = ObjectId('5162fab9c92b4c751e000274');
+var nick = ObjectId('512ef4805a67a8c507000001');
+//var collections = [db.levels, db.level.components, db.level.systems];
+var collection = db.levels;
+//var collection = db.level.components;
+//var collection = db.level.systems;
+var permission;
+
+collection.find({slug:{$exists:1}}).forEach(function(doc) {
+  print('--------------------------------------------------', doc.name);
+  var official = false;
+  var owner = null;
+  var changed = false;
+  for (var j in doc.permissions) {
+    permission = doc.permissions[j];
+    if(permission.access !== 'owner')
+      continue;
+    owner = permission.target;
+    if(owner === scott+'') {
+      print('Owner of', doc.name, 'is Scott');
+      official = true;
+    } 
+    else if(owner === nick+'') {
+      print('Owner of', doc.name, 'is Nick');
+      official = true;
+    }
+    else {
+      print('Owner of', doc.name, 'is', owner);
+    }
+  }
+  if(!doc.watchers) {
+    print('Init watchers, was', doc.watchers);
+    doc.watchers = [];
+  }
+  if(official) {
+    var hasNick = false;
+    var hasScott = false;
+    for(var k in doc.watchers) {
+      var watcher = doc.watchers[k];
+      if(watcher.equals(nick)) hasNick = true;
+      if(watcher.equals(scott)) hasScott = true;
+    }
+    if(!hasNick) {
+      doc.watchers.push(nick);
+      print('Added Nick to', doc.name);
+      changed = true;
+    }
+    if(!hasScott) {
+      doc.watchers.push(scott);
+      print('Added Scott to', doc.name);
+      changed = true;
+    }
+  }
+  else {
+    var hasOwner = false;
+    for(var l in doc.watchers) {
+      var watcher = doc.watchers[l];
+      if(watcher+'' === owner) hasOwner = true;
+    }
+    if(!hasOwner) {
+      doc.watchers.push(ObjectId(owner));
+      print('Added owner to', doc.name);
+      changed = true;
+    }
+  }
+  if(changed) {
+    print('Changed, so saving');
+    collection.save(doc);
+  }
+});
\ No newline at end of file
diff --git a/scripts/mongodb/queries/patches.js b/scripts/mongodb/queries/patches.js
new file mode 100644
index 000000000..418ebbe18
--- /dev/null
+++ b/scripts/mongodb/queries/patches.js
@@ -0,0 +1,13 @@
+// Finds all patches and denorms their target names and creators.
+
+var patches = db.patches.find({status:'pending'}).toArray();
+for(var i in patches) {
+  var patch = patches[i];
+  var collection = null;
+  if(patch.target.collection === 'level') collection = db.levels;
+  if(patch.target.collection === 'level_component') collection = db.level.components;
+  if(patch.target.collection === 'level_system') collection = db.level.systems;
+  var target = collection.findOne({original:patch.target.original, name:{$exists:true}});
+  var creator = db.users.findOne({_id:patch.creator});
+  print(target.name, 'made by', creator.name);
+ }
\ No newline at end of file
diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee
index caddfedc0..f7723f859 100644
--- a/server/users/user_handler.coffee
+++ b/server/users/user_handler.coffee
@@ -105,7 +105,8 @@ UserHandler = class UserHandler extends Handler
     (req, user, callback) ->
       return callback(null, req, user) unless req.body.name
       nameLower = req.body.name?.toLowerCase()
-      # return callback(null, req, user) if nameLower is user.get('nameLower')
+      return callback(null, req, user) unless nameLower
+      return callback(null, req, user) if nameLower is user.get('nameLower') and not user.get('anonymous')
       User.findOne({nameLower:nameLower,anonymous:false}).exec (err, otherUser) ->
         log.error "Database error setting user name: #{err}" if err
         return callback(res:'Database error.', code:500) if err
diff --git a/test/server/functional/user.spec.coffee b/test/server/functional/user.spec.coffee
index 7ce89886d..03a88242d 100644
--- a/test/server/functional/user.spec.coffee
+++ b/test/server/functional/user.spec.coffee
@@ -20,7 +20,7 @@ describe 'Server user object', ->
     expect(user.isEmailSubscriptionEnabled('adventurerNews')).toBeTruthy()
     expect(user.isEmailSubscriptionEnabled('generalNews')).toBeFalsy()
     done()
-    
+
   it 'maintains the old subs list if it\'s around', (done) ->
     user = new User()
     user.set 'emailSubscriptions', ['tester']
@@ -33,7 +33,7 @@ describe 'User.updateMailChimp', ->
     GLOBAL.mc =
       lists:
         subscribe: callback
-  
+
   it 'uses emails to determine what to send to MailChimp', (done) ->
     makeMC (params) ->
       expect(JSON.stringify(params.merge_vars.groupings[0].groups)).toBe(JSON.stringify(['Announcements']))
@@ -41,7 +41,7 @@ describe 'User.updateMailChimp', ->
 
     user = new User({emailSubscriptions:['announcement'], email:'tester@gmail.com'})
     User.updateMailChimp(user)
-          
+
 describe 'POST /db/user', ->
 
   createAnonNameUser = (done)->
@@ -191,7 +191,16 @@ ghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghl
         form.append('_id', String(admin._id))
         form.append('email', joe.get('email').toUpperCase())
 
-  it 'works', (done) ->
+  it 'does not care if you include your existing name', (done) ->
+    unittest.getNormalJoe (joe) ->
+      req = request.put getURL(urlUser+'/'+joe._id), (err, res) ->
+        expect(res.statusCode).toBe(200)
+        done()
+      form = req.form()
+      form.append('_id', String(joe._id))
+      form.append('name', 'Joe')
+
+  it 'accepts name and email changes', (done) ->
     unittest.getNormalJoe (joe) ->
       req = request.put getURL(urlUser), (err, res) ->
         expect(res.statusCode).toBe(200)
@@ -205,7 +214,6 @@ ghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghl
       form.append('email', 'New@email.com')
       form.append('name', 'Wilhelm')
 
-
 describe 'GET /db/user', ->
 
   it 'logs in as admin', (done) ->