diff --git a/app/assets/javascripts/workers/worker_world.js b/app/assets/javascripts/workers/worker_world.js
index d9deafe07..171cb0be8 100644
--- a/app/assets/javascripts/workers/worker_world.js
+++ b/app/assets/javascripts/workers/worker_world.js
@@ -311,6 +311,7 @@ self.setupDebugWorldToRunUntilFrame = function (args) {
             self.debugWorld.levelSessionIDs = args.levelSessionIDs;
             self.debugWorld.submissionCount = args.submissionCount;
             self.debugWorld.flagHistory = args.flagHistory;
+            self.debugWorld.difficulty = args.difficulty;
             if (args.level)
                 self.debugWorld.loadFromLevel(args.level, true);
             self.debugWorld.debugging = true;
@@ -371,6 +372,7 @@ self.runWorld = function runWorld(args) {
     self.world.levelSessionIDs = args.levelSessionIDs;
     self.world.submissionCount = args.submissionCount;
     self.world.flagHistory = args.flagHistory || [];
+    self.world.difficulty = args.difficulty || 0;
     if(args.level)
       self.world.loadFromLevel(args.level, true);
     self.world.preloading = args.preload;
diff --git a/app/core/initialize.coffee b/app/core/initialize.coffee
index 2bceda950..3d42b664a 100644
--- a/app/core/initialize.coffee
+++ b/app/core/initialize.coffee
@@ -1,5 +1,5 @@
 Backbone.Mediator.setValidationEnabled false
-app = require 'core/application'
+app = null
 
 channelSchemas =
   'auth': require 'schemas/subscriptions/auth'
@@ -21,6 +21,15 @@ definitionSchemas =
   'misc': require 'schemas/definitions/misc'
 
 init = ->
+  return if app
+  if not window.userObject._id
+    $.ajax('/auth/whoami', {success: (res) ->
+      window.userObject = res
+      init()
+    })
+    return
+
+  app = require 'core/application'
   setupConsoleLogging()
   watchForErrors()
   setUpIOSLogging()
@@ -34,8 +43,6 @@ init = ->
   handleNormalUrls()
   setUpMoment() # Set up i18n for moment
 
-module.exports.init = init = _.once init
-
 handleNormalUrls = ->
   # http://artsy.github.com/blog/2012/06/25/replacing-hashbang-routes-with-pushstate/
   $(document).on 'click', "a[href^='/']", (event) ->
diff --git a/app/lib/Angel.coffee b/app/lib/Angel.coffee
index 8aec202f2..44c54c562 100644
--- a/app/lib/Angel.coffee
+++ b/app/lib/Angel.coffee
@@ -39,7 +39,7 @@ module.exports = class Angel extends CocoClass
 
   # say: debugging stuff, usually off; log: important performance indicators, keep on
   say: (args...) -> #@log args...
-  log: -> 
+  log: ->
     # console.info.apply is undefined in IE9, CofeeScript splats invocation won't work.
     # http://stackoverflow.com/questions/5472938/does-ie9-support-console-log-and-is-it-a-real-function
     message = "|#{@shared.godNick}'s #{@nick}|"
@@ -246,6 +246,7 @@ module.exports = class Angel extends CocoClass
     work.testWorld.levelSessionIDs = work.levelSessionIDs
     work.testWorld.submissionCount = work.submissionCount
     work.testWorld.flagHistory = work.flagHistory ? []
+    work.testWorld.difficulty = work.difficulty
     testWorld.loadFromLevel work.level
     work.testWorld.preloading = work.preload
     work.testWorld.headless = work.headless
diff --git a/app/lib/God.coffee b/app/lib/God.coffee
index cdaabdcd7..b22f6dcd7 100644
--- a/app/lib/God.coffee
+++ b/app/lib/God.coffee
@@ -63,6 +63,7 @@ module.exports = class God extends CocoClass
   onTomeCast: (e) ->
     @lastSubmissionCount = e.submissionCount
     @lastFlagHistory = (flag for flag in e.flagHistory when flag.source isnt 'code')
+    @lastDifficulty = e.difficulty
     @createWorld e.spells, e.preload, e.realTime
 
   createWorld: (spells, preload, realTime) ->
@@ -92,6 +93,7 @@ module.exports = class God extends CocoClass
       levelSessionIDs: @levelSessionIDs
       submissionCount: @lastSubmissionCount
       flagHistory: @lastFlagHistory
+      difficulty: @lastDifficulty
       goals: @angelsShare.goalManager?.getGoals()
       headless: @angelsShare.headless
       preload: preload
@@ -123,6 +125,7 @@ module.exports = class God extends CocoClass
         levelSessionIDs: @levelSessionIDs
         submissionCount: @lastSubmissionCount
         flagHistory: @lastFlagHistory
+        difficulty: @lastDifficulty
         goals: @goalManager?.getGoals()
         frame: args.frame
         currentThangID: args.thangID
diff --git a/app/lib/LevelLoader.coffee b/app/lib/LevelLoader.coffee
index c6b9ec3b8..f4f442ff0 100644
--- a/app/lib/LevelLoader.coffee
+++ b/app/lib/LevelLoader.coffee
@@ -374,6 +374,7 @@ module.exports = class LevelLoader extends CocoClass
     @world.levelSessionIDs = if @opponentSessionID then [@sessionID, @opponentSessionID] else [@sessionID]
     @world.submissionCount = @session?.get('state')?.submissionCount ? 0
     @world.flagHistory = @session?.get('state')?.flagHistory ? []
+    @world.difficulty = @session?.get('state')?.difficulty ? 0
     serializedLevel = @level.serialize(@supermodel, @session, @opponentSession)
     @world.loadFromLevel serializedLevel, false
     console.log 'World has been initialized from level loader.'
diff --git a/app/lib/world/world.coffee b/app/lib/world/world.coffee
index a4ee8aaea..0c3d3332c 100644
--- a/app/lib/world/world.coffee
+++ b/app/lib/world/world.coffee
@@ -360,7 +360,7 @@ module.exports = class World
     #console.log "... world serializing frames from", startFrame, "to", endFrame, "of", @totalFrames
     [transferableObjects, nontransferableObjects] = [0, 0]
     delete flag.processed for flag in @flagHistory
-    o = {totalFrames: @totalFrames, maxTotalFrames: @maxTotalFrames, frameRate: @frameRate, dt: @dt, victory: @victory, userCodeMap: {}, trackedProperties: {}, flagHistory: @flagHistory}
+    o = {totalFrames: @totalFrames, maxTotalFrames: @maxTotalFrames, frameRate: @frameRate, dt: @dt, victory: @victory, userCodeMap: {}, trackedProperties: {}, flagHistory: @flagHistory, difficulty: @difficulty}
     o.trackedProperties[prop] = @[prop] for prop in @trackedProperties or []
 
     for thangID, methods of @userCodeMap
@@ -467,7 +467,7 @@ module.exports = class World
             w.userCodeMap[thangID][methodName][aetherStateKey] = serializedAether[aetherStateKey]
     else
       w = new World o.userCodeMap, classMap
-    [w.totalFrames, w.maxTotalFrames, w.frameRate, w.dt, w.scriptNotes, w.victory, w.flagHistory] = [o.totalFrames, o.maxTotalFrames, o.frameRate, o.dt, o.scriptNotes ? [], o.victory, o.flagHistory]
+    [w.totalFrames, w.maxTotalFrames, w.frameRate, w.dt, w.scriptNotes, w.victory, w.flagHistory, w.difficulty] = [o.totalFrames, o.maxTotalFrames, o.frameRate, o.dt, o.scriptNotes ? [], o.victory, o.flagHistory, o.difficulty]
     w[prop] = val for prop, val of o.trackedProperties
 
     perf.t1 = now()
diff --git a/app/locale/de-DE.coffee b/app/locale/de-DE.coffee
index 3e7f6e88d..6aacf321b 100644
--- a/app/locale/de-DE.coffee
+++ b/app/locale/de-DE.coffee
@@ -146,13 +146,13 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
     fork: "Fork"
     play: "Spiel starten" # When used as an action verb, like "Play next level"
     retry: "Erneut versuchen"
-#    actions: "Actions"
-#    info: "Info"
-#    help: "Help"
+    actions: "Aktionen"
+    info: "Info"
+    help: "Hilfe"
     watch: "Beobachten"
     unwatch: "Nicht beobachten"
     submit_patch: "Patch einreichen"
-#    submit_changes: "Submit Changes"
+    submit_changes: "Änderungen einreichen"
 
   general:
     and: "und"
@@ -160,16 +160,16 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
     date: "Datum"
     body: "Inhalt"
     version: "Version"
-#    submitter: "Submitter"
-#    submitted: "Submitted"
+    submitter: "Übermittler"
+    submitted: "Übermittelt"
     commit_msg: "Übertrage Nachricht"
-#    review: "Review"
+    review: "Prüfen"
     version_history: "Versionshistorie"
     version_history_for: "Versionsgeschichte für: "
-#    select_changes: "Select two changes below to see the difference."
-#    undo: "Undo (Ctrl+Z)"
-#    redo: "Redo (Ctrl+Shift+Z)"
-#    play_preview: "Play preview of current level"
+    select_changes: "Wähle zwei Änderungen unten um den Unterschied sehen zu können."
+    undo: "Rückgängig (Ctrl+Z)"
+    redo: "Wiederholen (Ctrl+Shift+Z)"
+    play_preview: "Spiele eine Vorschau des momentanen Levels"
     result: "Ergebnis"
     results: "Ergebnisse"
     description: "Beschreibung"
@@ -191,7 +191,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
     medium: "Mittel"
     hard: "Schwer"
     player: "Spieler"
-    player_level: "Level" # Like player level 5, not like level: Dungeons of Kithgard
+    player_level: "Stufe" # Like player level 5, not like level: Dungeons of Kithgard
 
   units:
     second: "Sekunde"
@@ -270,7 +270,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
     loading_ready: "Bereit!"
     loading_start: "Starte Level"
     problem_alert_title: "Repariere deinen Code"
-#    problem_alert_help: "Help"
+    problem_alert_help: "Hilfe"
     time_current: "Aktuell"
     time_total: "Total"
     time_goto: "Gehe zu"
@@ -295,10 +295,10 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
     tip_great_responsibility: "Mit großen Programmierfähigkeiten kommt große Verantwortung."
     tip_munchkin: "Wenn du dein Gemüse nicht isst, besucht dich ein Zwerg während du schläfst."
     tip_binary: "Es gibt auf der Welt nur 10 Arten von Menschen: die die Binär verstehen und die die nicht."
-    tip_commitment_yoda: "Ein Programmier muss die größte Hingabe haben, den ernstesten Verstand. ~ Yoda"
-    tip_no_try: "Tu. Oder tu nicht. Es gibt kein Versuchen. - Yoda"
+    tip_commitment_yoda: "Ein Programmier muss die größte Hingabe haben, den ernstesten Verstand. - Yoda"
+    tip_no_try: "Tun oder nicht tun. Es gibt kein Versuchen. - Yoda"
     tip_patience: "Geduld du haben musst, junger Padawan. - Yoda"
-    tip_documented_bug: "Ein dokumentierter Fehler ist kein Fehler; er ist ein Merkmal."
+    tip_documented_bug: "Ein dokumentierter Fehler ist kein Fehler; er ist ein Besonderheit."
     tip_impossible: "Es wirkt immer unmöglich bis es vollbracht ist. - Nelson Mandela"
     tip_talk_is_cheap: "Reden ist billig. Zeig mir den Code. - Linus Torvalds"
     tip_first_language: "Das Schwierigste, das du jemals lernen wirst, ist die erste Programmiersprache. - Alan Kay"
@@ -307,15 +307,15 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
     tip_premature_optimization: "Vorzeitige Optimierung ist die Wurzel allen Übels (oder mindestens des meisten) bei der Programmierung - Donald Knuth"
     tip_brute_force: "Verwende im Zweifelsfall rohe Gewalt. - Ken Thompson"
     tip_extrapolation: "Es gibt nur zwei Sorten Menschen, diejenigen die aus unvollständigen Informationen Schlüsse ziehen können, ..."
-#    tip_superpower: "Coding is the closest thing we have to a superpower."
+    tip_superpower: "Programmieren ist das näheste zu einer Superkraft was wir haben."
 
   game_menu:
     inventory_tab: "Inventar"
     save_load_tab: "Speicher/Lade"
     options_tab: "Einstellungen"
     guide_tab: "Handbuch"
-#    guide_video_tutorial: "Video Tutorial"
-#    guide_tips: "Tips"
+    guide_video_tutorial: "Video Anleitung"
+    guide_tips: "Hinweise"
     multiplayer_tab: "Mehrspieler"
     auth_tab: "Registrieren"
     inventory_caption: "Rüste deinen Helden aus"
@@ -329,7 +329,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
   inventory:
     choose_inventory: "Gegenstände ausrüsten"
     equipped_item: "Hinzugefügt"
-#    required_purchase_title: "Required"
+    required_purchase_title: "Benötigt"
     available_item: "Verfügbar"
     restricted_title: "Eingeschränkt"
     should_equip: "(Doppelklick zum Hinzufügen)"
@@ -349,7 +349,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
     prompt_title: "Nicht genug Edelsteine"
     prompt_body: "Benötigst du mehr?"
     prompt_button: "Laden betreten"
-#    recovered: "Previous gems purchase recovered. Please refresh the page."
+    recovered: "Vorhergegangener Edelsteinkauf rückgängig gemacht. Aktualisiere bitte die Seite."
 
   subscribe:
     subscribe_title: "Abonnieren"
@@ -411,7 +411,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
     current_value: "Aktueller Wert"
     default_value: "Standardwert"
     parameters: "Parameter"
-#    returns: "Returns"
+    returns: "Gibt zurück"
     granted_by: "Gewährt durch"
 
   save_load:
@@ -449,7 +449,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
     why_paragraph_2_prefix: "Darum geht's beim Programmieren. Es soll Spaß machen. Nicht so einen Spaß wie"
     why_paragraph_2_italic: "jau, 'ne Plakette"
     why_paragraph_2_center: "sondern Spaß wie"
-    why_paragraph_2_italic_caps: "NEIN MUTTI ICH MUSS NOCH DEN LEVEL BEENDEN !"
+    why_paragraph_2_italic_caps: "NEIN MUTTI ICH MUSS NOCH DAS LEVEL BEENDEN !"
     why_paragraph_2_suffix: "Deshalb ist CodeCombat ein Multiplayerspiel und kein spielähnlicher Kurs. Wir werden nicht aufhören bis du nicht mehr aufhören kannst -- nur diesmal ist das eine gute Sache."
     why_paragraph_3: "Wenn dich Spiele süchtig machen, dann lass dich von diesem süchtig machen und werde ein Zauberer des Technologiezeitalters."
     press_title: "Blogger/Presse"
@@ -482,14 +482,14 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
     forum_prefix: "Für alle öffentlichen Themen, benutze stattdessen "
     forum_page: "unser Forum"
     forum_suffix: "."
-#    faq_prefix: "There's also a"
-#    faq: "FAQ"
-#    subscribe_prefix: "If you need help figuring out a level, please"
-#    subscribe: "buy a CodeCombat subscription"
-#    subscribe_suffix: "and we'll be happy to help you with your code."
-#    subscriber_support: "Since you're a CodeCombat subscriber, your email will get our priority support."
-#    screenshot_included: "Screenshot included."
-#    where_reply: "Where should we reply?"
+    faq_prefix: "Es gibt auch ein"
+    faq: "FAQ"
+    subscribe_prefix: "Wenn du hilfe brauchst ein Level zu lösen, bitte"
+    subscribe: "kaufe ein CodeCombat Abonnement"
+    subscribe_suffix: "und wir werden dir gerne bei deinem Code helfen."
+    subscriber_support: "Da du ein CodeCombat Abonnent bist, bekommt deine E-Mail Priorität."
+    screenshot_included: "Bildschirmfoto hinzugefügt."
+    where_reply: "Wohin sollen wir antworten?"
     send: "Sende Feedback"
     contact_candidate: "Kontaktiere Kandidaten" # Deprecated
     recruitment_reminder: "Benutzen Sie dieses Formular um Kontakt zu Kandidaten aufzunehmen, an denen Sie interessiert sind. Bedenken Sie das CodeCombat 15% des ersten Jahresgehaltes berechnet. Diese Gebühr wird fällig wenn Sie den Kandidaten einstellen und ist für 90 Tage rückerstattungsfähig, sollte der Mitarbeiter nicht eingestellt bleiben. Mitarbeiter die für Teilzeit, Remote oder eine Auftragsarbeit eingestellt werden sind kostenlos, das gilt auch für Praktikanten." # Deprecated
@@ -573,22 +573,22 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
   classes:
     archmage_title: "Erzmagier"
     archmage_title_description: "(Programmierer)"
-#    archmage_summary: "If you are a developer interested in coding educational games, become an archmage to help us build CodeCombat!"
+    archmage_summary: "Wenn du ein Entwickler bist der daran interessiert ist Lernspiele zu programmieren, werde ein Erzmagier um uns zu helfen CodeCombat zu erschaffen!"
     artisan_title: "Handwerker"
     artisan_title_description: "(Level Entwickler)"
-#    artisan_summary: "Build and share levels for you and your friends to play. Become an Artisan to learn the art of teaching others to program."
+    artisan_summary: "Erschaffe und teile Level zum spielen für dich und deine Freunde. Werde ein Handwerker um die Kunst zu lernen anderen Programmieren zu lehren."
     adventurer_title: "Abenteurer"
     adventurer_title_description: "(Level Spieltester)"
-#    adventurer_summary: "Get our new levels (even our subscriber content) for free one week early and help us work out bugs before our public release."
+    adventurer_summary: "Bekomme unsere neuen Level (sogar unser Abonnement Inhalt) kostenlos eine Woche früher und hilf uns Fehler vor der Veröffentlichung zu finden."
     scribe_title: "Schreiber"
     scribe_title_description: "(Artikel Editor)"
-#    scribe_summary: "Good code needs good documentation. Write, edit, and improve the docs read by millions of players across the globe."
+    scribe_summary: "Guter Code braucht gute Dokumentation. Schreibe, bearbeite and verbessere die, von weltweit Millionen von Spielern, gelesenen Dokumentationen."
     diplomat_title: "Diplomat"
     diplomat_title_description: "(Übersetzer)"
-#    diplomat_summary: "CodeCombat is localized in 45+ languages by our Diplomats. Help us out and contribute translations."
+    diplomat_summary: "CodeCombat wird in 45+ Sprachen von unseren Diplomaten übersetzt. Hilf uns und steuere Übersetzungen bei."
     ambassador_title: "Botschafter"
     ambassador_title_description: "(Support)"
-#    ambassador_summary: "Tame our forum users and provide direction for those with questions. Our ambassadors represent CodeCombat to the world."
+    ambassador_summary: "Zähme unsere Forum Benutzer und weise denen mit Fragen die Richtung. Unsere Botschafter repräsentieren CodeCombat vor der Welt."
 
   editor:
     main_title: "CodeCombat Editoren"
@@ -608,9 +608,9 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
     more: "Mehr"
     wiki: "Wiki"
     live_chat: "Live Chat"
-#    thang_main: "Main"
-#    thang_spritesheets: "Spritesheets"
-#    thang_colors: "Colors"
+    thang_main: "Main" # I'd keep it this way, everyone should get what it means
+    thang_spritesheets: "Sprite Palette"
+    thang_colors: "Farben"
     level_some_options: "Einige Einstellungsmöglichkeiten?"
     level_tab_thangs: "Thangs"
     level_tab_scripts: "Skripte"
@@ -657,7 +657,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
     achievement_query_misc: "Sonstige Schlüsselerfolge"
     achievement_query_goals: "Level Erfolge"
     level_completion: "abgeschlossene Level"
-#    pop_i18n: "Populate I18N"
+    pop_i18n: "Bevölkere I18N"
 
   article:
     edit_btn_preview: "Vorschau"
@@ -665,7 +665,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
 
   contribute:
     page_title: "Mitwirken"
-#    intro_blurb: "CodeCombat is 100% open source! Hundreds of dedicated players have helped us build the game into what it is today. Join us and write the next chapter in CodeCombat's quest to teach the world to code!"
+    intro_blurb: "CodeCombat ist zu 100% Open Source! Hunderte hingebungsvolle Spieler haben uns geholfen das Spiel zu dem zu machen was es heute ist. Trete uns bei und schreibe das nächste Kapitel in der Aufgabe von CodeCombat der Welt Programmieren zu lehren!"
     alert_account_message_intro: "Hey du!"
     alert_account_message: "Um Klassen-Emails abonnieren zu können, musst du dich zuerst anmelden."
     archmage_introduction: "Einer der größten Vorteile daran ein Spiel aufzubauen, ist es, dass so viele verschiedene Aspekte mit reinspielen. Grafiken, Sound, echtzeit Networking, Social Networking und natürlich viele der gewöhnlichen Aspekte des Programmierens, von low-level Datenbankmanagement und Server Administration bis hin zum Aufbau von Design und Interface. Es gibt viel zu tun und wenn du ein erfahrener Programmierer bist, mit einer Veranlagung dazu, wirklich knallhart bei CodeCombat einzutauchen, dann könnte diese Klasse etwas für dich sein. Wir würden uns wahnsinnig  über deine Hilfe dabei freuen, das beste Programmierspiel der Welt aufzubauen."
@@ -963,7 +963,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
     art_paragraph_1: "Für den Verweis auf CodeCombat, nenne und verlinke bitte die Website codecombat.com nahe der Quelle oder an der Stelle, wo es für das Medium angemessen ist. Zum Beispiel:"
     use_list_1: "Wenn in einem Film verwendet, nenne codecombat.com in den Credits/Abspann"
     use_list_2: "Wenn auf einer Webseite verwendet, füge einen Link nahe bei der Verwendung ein, z.B. unter einem Bild oder auf der generellen Beitragsseite, wo auch andere Creative Commons Werke und Open Source Software genannt wird, die auf der Seite verwendet wird. Wenn deutlich auf CodeCombat Bezug genommen wird, wie z.B. in einem Blogeintrag, in dem CodeCombat erwähnt wird, dann muss CodeCombat nicht separat belegt werden."
-#    art_paragraph_2: "If the content being used is created not by CodeCombat but instead by a user of codecombat.com, attribute them instead, and follow attribution directions provided in that resource's description if there are any."
+    art_paragraph_2: "Wenn der benutzte Inhalt nicht von CodeCombat sonder einem Benutzer von codecombat.com geschaffen wurde, schreibe es diesem stattdessen zu und folge den Anweisungen, wenn es welche gibt, in der Beschreibung dieses Inhalts."
     rights_title: "Rechte vorbehalten"
     rights_desc: "Alle Rechte vorbehalten für die Level selbst. Dies beinhaltet"
     rights_scripts: "Skripte"
@@ -1002,8 +1002,8 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
     done_editing: "Editierung beenden"
     profile_for_prefix: "Profil von "
     profile_for_suffix: ""
-#    featured: "Featured"
-#    not_featured: "Not Featured"
+    featured: "Featured" # I'd keep it that way, should be understandable by everyone
+    not_featured: "Not Featured" # Ref. Above
     looking_for: "Suche nach:"
     last_updated: "zuletzt geändert:"
     contact: "Kontakt"
@@ -1143,7 +1143,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
     candidate_years_experience: "Erfahrung (Jahre)"
     candidate_last_updated: "Zuletzt aktualisiert"
     candidate_who: "Wer"
-#    featured_developers: "Featured Developers"
+    featured_developers: "Mitwirkende Entwickler" # Meaning: Mitwirken -> Contribute, play a part
     other_developers: "Andere Entwickler"
     inactive_developers: "Inaktive Etwickler"
 
diff --git a/app/locale/en.coffee b/app/locale/en.coffee
index 26d8f38cd..c0268d09f 100644
--- a/app/locale/en.coffee
+++ b/app/locale/en.coffee
@@ -166,8 +166,10 @@
     version_history: "Version History"
     version_history_for: "Version History for: "
     select_changes: "Select two changes below to see the difference."
-    undo: "Undo (Ctrl+Z)"
-    redo: "Redo (Ctrl+Shift+Z)"
+    undo_prefix: "Undo"
+    undo_shortcut: "(Ctrl+Z)"
+    redo_prefix: "Redo"
+    redo_shortcut: "(Ctrl+Shift+Z)"
     play_preview: "Play preview of current level"
     result: "Result"
     results: "Results"
diff --git a/app/locale/es-ES.coffee b/app/locale/es-ES.coffee
index 4de941e35..9f60ff8d2 100644
--- a/app/locale/es-ES.coffee
+++ b/app/locale/es-ES.coffee
@@ -56,9 +56,9 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
     items: "Objetos" # Tooltip on item shop button from /play
     unlock: "Desbloquear" # For purchasing items and heroes
     confirm: "Confirmar"
-#    owned: "Owned" # For items you own
-#    locked: "Locked"
-#    purchasable: "Purchasable" # For a hero you unlocked but haven't purchased
+    owned: "Lo Posees" # For items you own
+    locked: "Bloqueado"
+    purchasable: "Comprable" # For a hero you unlocked but haven't purchased
     available: "Disponible"
     skills_granted: "Habilidades concedidas" # Property documentation details
     heroes: "Heroes" # Tooltip on hero shop button from /play
@@ -69,7 +69,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
     change_hero: "Seleccionar Heroe" # Go back from choose inventory to choose hero
     choose_inventory: "Equipar Objetos"
     buy_gems: "Comprar Joyas"
-#    campaign_desert: "Desert Campaign"
+    campaign_desert: "Campaña del Desierto"
     campaign_forest: "Campaña del Bosque"
     campaign_dungeon: "Campaña del Calabozo"
     subscription_required: "Suscripción requerida"
@@ -80,7 +80,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
     level_difficulty: "Dificultad: "
     campaign_beginner: "Campaña de Principiante"
     awaiting_levels_adventurer_prefix: "Liberamos cinco niveles cada semana."
-#    awaiting_levels_adventurer: "Sign up as an Adventurer"
+    awaiting_levels_adventurer: "Regístrate como Aventurero"
     awaiting_levels_adventurer_suffix: "para ser el primero en jugar nuevos niveles."
     choose_your_level: "Elige tu nivel" # The rest of this section is the old play view at /play-old and isn't very important.
     adventurer_prefix: "Puedes elegir cualquier pantalla o charlar en "
@@ -102,14 +102,14 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
     log_in: "Entrar"
     logging_in: "Entrando..."
     log_out: "Salir"
-#    forgot_password: "Forgot your password?"
+    forgot_password: "¿Olvidaste tu contraseña?"
     authenticate_gplus: "Autenticar G+"
     load_profile: "Cargar perfil G+"
     load_email: "Cargar correo G+"
     finishing: "Finalizando"
-#    sign_in_with_facebook: "Sign in with Facebook"
-#    sign_in_with_gplus: "Sign in with G+"
-#    signup_switch: "Want to create an account?"
+    sign_in_with_facebook: "Accede usando Facebook"
+    sign_in_with_gplus: "Accede usando G+"
+    signup_switch: "¿Quieres crear una cuenta?"
 
   signup:
     email_announcements: "Recibir noticias por correo electrónico"
@@ -118,7 +118,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
     log_in: "Iniciar sesión con contraseña"
     social_signup: "O, puedes acceder a través de tu cuenta de Facebook o G+:"
     required: "Tienes que estar reginstrado antes de poder seguir por aquí."
-#    login_switch: "Already have an account?"
+    login_switch: "¿Ya tienes una cuenta?"
 
   recover:
     recover_account_title: "Recuperar Cuenta"
diff --git a/app/locale/hu.coffee b/app/locale/hu.coffee
index 509e7c4f0..ce85e3afd 100644
--- a/app/locale/hu.coffee
+++ b/app/locale/hu.coffee
@@ -169,7 +169,7 @@ module.exports = nativeDescription: "magyar", englishDescription: "Hungarian", t
     select_changes: "Válassz két lehetőséget alul, hogy lásd a különbséget."
     undo: "Vissza (Ctrl+Z)"
     redo: "Újra (Ctrl+Shift+Z)"
-#    play_preview: "Play preview of current level"
+    play_preview: "Aktuális szint előnézete"
     result: "Eredmény"
     results: "Eredmények"
     description: "Leírás"
@@ -269,8 +269,8 @@ module.exports = nativeDescription: "magyar", englishDescription: "Hungarian", t
     keyboard_shortcuts: "Billentyűparancsok"
     loading_ready: "Kész!"
     loading_start: "Szint kezdése"
-    problem_alert_title: "igazítsd ki a Kódod"
-#    problem_alert_help: "Help"
+    problem_alert_title: "Igazítsd ki a Kódod"
+    problem_alert_help: "Segítség"
     time_current: "Most:"
     time_total: "Maximum:"
     time_goto: "Menj"
@@ -307,15 +307,15 @@ module.exports = nativeDescription: "magyar", englishDescription: "Hungarian", t
     tip_premature_optimization: "Minden rossz gyökere a korai optimizáció. - Donald Knuth"
     tip_brute_force: "Ha kérdésesa helyzet, használj nyers erőt. - Ken Thompson"
     tip_extrapolation: "Csak két fajta ember létezik. Az egyik, aki extrapolál hiányos adatokból..."
-#    tip_superpower: "Coding is the closest thing we have to a superpower."
+    tip_superpower: "A programozás képessége van legközelebb a szuperképességekhez."
 
   game_menu:
     inventory_tab: "Raktár"
     save_load_tab: "Ment/Betölt"
     options_tab: "Beállítások"
     guide_tab: "Vezérfonal"
-#    guide_video_tutorial: "Video Tutorial"
-#    guide_tips: "Tips"
+    guide_video_tutorial: "Bevezető videó"
+    guide_tips: "Tippek"
     multiplayer_tab: "Többjátékos"
     auth_tab: "Iratkozz fel!"
     inventory_caption: "Szereld fel a hősöd!"
@@ -329,7 +329,7 @@ module.exports = nativeDescription: "magyar", englishDescription: "Hungarian", t
   inventory:
     choose_inventory: "felszerelési tárgyak"
     equipped_item: "Választott"
-#    required_purchase_title: "Required"
+    required_purchase_title: "Szükséges"
     available_item: "Elérhető"
     restricted_title: "Limitált"
     should_equip: "(felszereléshez dupla katt)"
@@ -351,57 +351,57 @@ module.exports = nativeDescription: "magyar", englishDescription: "Hungarian", t
     prompt_button: "Lépj be a boltba"
 #    recovered: "Previous gems purchase recovered. Please refresh the page."
 
-#  subscribe:
-#    subscribe_title: "Subscribe"
-#    unsubscribe: "Unsubscribe"
-#    levels: "Get more practice with bonus levels!"
-#    heroes: "More powerful heroes!"
-#    gems: "3500 bonus gems every month!"
-#    items: "Over 250 bonus items!"
-#    parents: "For Parents"
-#    parents_title: "Your child will learn to code."
-#    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
-#    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
-#    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
-#    stripe_description: "Monthly Subscription"
-#    subscription_required_to_play: "You'll need a subscription to play this level."
+  subscribe:
+    subscribe_title: "Feliratkozás"
+    unsubscribe: "Leiratkozás"
+    levels: "Gyakorolj a bónusz szinteken!"
+    heroes: "Még erősebb hősök!"
+    gems: "3500 búnusz drágakő havonta!"
+    items: "Több mint 250 bónusz tárgy!"
+    parents: "Szülőknek"
+    parents_title: "A gyereke programozni tanul majd."
+    parents_blurb1: "A CodeCombattal a gyereke valódi programozási feladatokon keresztül tanul. Egyszerű utasításokkal kezdenek, aztán további témákba is betekintést kapnak."
+    parents_blurb2: "Havonta 9,99 USD-ért, minden héten új kihívások elé állítjuk őket és személyre szóló emailes támogatást nyújtanak enkik profi programozók."
+    parents_blurb3: "100%-os pénzvisszafizetés garancia: 1-kattintásossal leiratkozhat."
+    subscribe_button: "Iratkozzon fel most"
+    stripe_description: "Havi feliratkozás"
+    subscription_required_to_play: "Ehhez a szinthez fel kell iratkoznod."
 
-#  choose_hero:
-#    choose_hero: "Choose Your Hero"
-#    programming_language: "Programming Language"
-#    programming_language_description: "Which programming language do you want to use?"
-#    default: "Default"
-#    experimental: "Experimental"
-#    python_blurb: "Simple yet powerful, great for beginners and experts."
-#    javascript_blurb: "The language of the web. (Not the same as Java.)"
-#    coffeescript_blurb: "Nicer JavaScript syntax."
-#    clojure_blurb: "A modern Lisp."
-#    lua_blurb: "Game scripting language."
-#    io_blurb: "Simple but obscure."
-#    status: "Status"
-#    weapons: "Weapons"
-#    weapons_warrior: "Swords - Short Range, No Magic"
-#    weapons_ranger: "Crossbows, Guns - Long Range, No Magic"
-#    weapons_wizard: "Wands, Staffs - Long Range, Magic"
-#    attack: "Damage" # Can also translate as "Attack"
-#    health: "Health"
-#    speed: "Speed"
-#    regeneration: "Regeneration"
-#    range: "Range" # As in "attack or visual range"
-#    blocks: "Blocks" # As in "this shield blocks this much damage"
-#    backstab: "Backstab" # As in "this dagger does this much backstab damage"
-#    skills: "Skills"
-#    available_for_purchase: "Available for Purchase" # Shows up when you have unlocked, but not purchased, a hero in the hero store
-#    level_to_unlock: "Level to unlock:" # Label for which level you have to beat to unlock a particular hero (click a locked hero in the store to see)
-#    restricted_to_certain_heroes: "Only certain heroes can play this level."
+  choose_hero:
+    choose_hero: "Válassz hőst."
+    programming_language: "Programnyelv"
+    programming_language_description: "Melyik programnyelvet akarod használni?"
+    default: "Alapbeállítás"
+    experimental: "Kísérleti"
+    python_blurb: "Egyszerű és mégis hatékony, kezdőknek és szakértőknek is."
+    javascript_blurb: "Az internet nyelve. (Nem azonos a Javaval.)"
+    coffeescript_blurb: "Szerethetőbb JavaScript szintaxis."
+    clojure_blurb: "A modern Lisp."
+    lua_blurb: "Játék programozó nyelv"
+    io_blurb: "Egyszerű, de különleges."
+    status: "Státusz"
+    weapons: "Fegyverek"
+    weapons_warrior: "Kardok - Rövid hatótávolság, mágikus erő nélkül."
+    weapons_ranger: "Számszeríj, Fegyverek - Nagy hatótávolság, mágikus erő nélkül."
+    weapons_wizard: "Pálcák és Botok - Nagy hatótávolság és mágikus erő."
+    attack: "Támadóérték" # Can also translate as "Attack"
+    health: "Élet"
+    speed: "Sebesség"
+    regeneration: "Gyógyulás"
+    range: "Hatótávolság" # As in "attack or visual range"
+    blocks: "Blokkolás értéke" # As in "this shield blocks this much damage"
+    backstab: "Visszavágás" # As in "this dagger does this much backstab damage"
+    skills: "Képességek"
+    available_for_purchase: "Megvehető" # Shows up when you have unlocked, but not purchased, a hero in the hero store
+    level_to_unlock: "Szükséges szintek:" # Label for which level you have to beat to unlock a particular hero (click a locked hero in the store to see)
+    restricted_to_certain_heroes: "Csak bizonyos hős játszhatja ezt a szintet."
 
   skill_docs:
     writable: "írható" # Hover over "attack" in Your Skills while playing a level to see most of this
     read_only: "csak olvasható"
     action_name: "név"
     action_cooldown: "Kitart"
-#    action_specific_cooldown: "Cooldown"
+    action_specific_cooldown: "Újratöltés"
     action_damage: "Sebzés"
     action_range: "Lőtávolság"
     action_radius: "Körzet"
@@ -447,7 +447,7 @@ module.exports = nativeDescription: "magyar", englishDescription: "Hungarian", t
     why_codecombat: "CodeCombat, de miért?"
     why_paragraph_1: "Ha programozni akarsz tanulni, nem kellenek hozzá tanórák. Csak írnod kell egy csomó kódot és jól érezned magad közben."
     why_paragraph_2_prefix: "Erről szól a programozás. Buli lesz. Nem viccelek."
-#    why_paragraph_2_italic: "yay a badge"
+    why_paragraph_2_italic: "ezaz, kitüntetés"
 #    why_paragraph_2_center: "but fun like"
     why_paragraph_2_italic_caps: "NE ANYA, BE KELL FEJEZNEM A SZINTET!"
 #    why_paragraph_2_suffix: "That's why CodeCombat is a multiplayer game, not a gamified lesson course. We won't stop until you can't stop--but this time, that's a good thing."
@@ -573,51 +573,51 @@ module.exports = nativeDescription: "magyar", englishDescription: "Hungarian", t
   classes:
     archmage_title: "Főmágus"
     archmage_title_description: "(Kódoló)"
-#    archmage_summary: "If you are a developer interested in coding educational games, become an archmage to help us build CodeCombat!"
+    archmage_summary: "Ha oktatási célú játékokban érdekelt fejlesztő vagy csatlakozz főmágusként a CodeCombat csapatához!"
     artisan_title: "Alkotóművész"
     artisan_title_description: "(Szint Építő)"
-#    artisan_summary: "Build and share levels for you and your friends to play. Become an Artisan to learn the art of teaching others to program."
+    artisan_summary: "Építs és ossz meg szinteket a barátaiddal. Legyél alkotóművész, aki másokat tanít programozni."
     adventurer_title: "Kalandor"
     adventurer_title_description: "(Játékteszter)"
-#    adventurer_summary: "Get our new levels (even our subscriber content) for free one week early and help us work out bugs before our public release."
+    adventurer_summary: "Szerezd meg az új szinteket, meg a feliratkozással elérhetőeket is egy héttel korábban és ingyen, és segíts a debugging-ban a hivatalos kiadás előtt."
     scribe_title: "Írnok"
     scribe_title_description: "(Cikk Szerkesztő)"
-#    scribe_summary: "Good code needs good documentation. Write, edit, and improve the docs read by millions of players across the globe."
+    scribe_summary: "A jó kód jó dokumentációt igényel. Írd, szekeszd és fejleszd a fájlokat, amelyeket milliók használnak a Föld minden pontjáról."
     diplomat_title: "Diplomata"
     diplomat_title_description: "(Fordító)"
-#    diplomat_summary: "CodeCombat is localized in 45+ languages by our Diplomats. Help us out and contribute translations."
+    diplomat_summary: "CodeCombat fordítása már több mint 45 nyelvre elkezdődött. Segítsd a fordításoddal te is a munkát."
     ambassador_title: "Nagykövet"
     ambassador_title_description: "(Támogató)"
-#    ambassador_summary: "Tame our forum users and provide direction for those with questions. Our ambassadors represent CodeCombat to the world."
+    ambassador_summary: "Vezesd a fórumozókat és mutass utat a kérdezőknek. A CodeCombatot a Nagykövetek reprezentálják a világban."
 
   editor:
     main_title: "CodeCombat Szerkesztők"
     article_title: "Cikk Szerkesztő"
-#    thang_title: "Thang Editor"
+    thang_title: "Dolog szerkesztő"
     level_title: "Szint Szerkesztő"
-#    achievement_title: "Achievement Editor"
+    achievement_title: "Eredmény szerkesztő"
     back: "Vissza"
 #    revert: "Revert"
 #    revert_models: "Revert Models"
 #    pick_a_terrain: "Pick A Terrain"
-#    small: "Small"
-#    grassy: "Grassy"
-#    fork_title: "Fork New Version"
+    small: "Kicsi"
+    grassy: "Füves"
+    fork_title: "Új Verzió villára vétele"
 #    fork_creating: "Creating Fork..."
-#    generate_terrain: "Generate Terrain"
+    generate_terrain: "Terület generálása"
     more: "Több"
     wiki: "Tudásbázis"
     live_chat: "Élő cset"
-#    thang_main: "Main"
+    thang_main: "Főoldal"
 #    thang_spritesheets: "Spritesheets"
 #    thang_colors: "Colors"
 #    level_some_options: "Some Options?"
 #    level_tab_thangs: "Thangs"
 #    level_tab_scripts: "Scripts"
-#    level_tab_settings: "Settings"
+    level_tab_settings: "Beállítások"
 #    level_tab_components: "Components"
 #    level_tab_systems: "Systems"
-#    level_tab_docs: "Documentation"
+    level_tab_docs: "Dokumentáció"
 #    level_tab_thangs_title: "Current Thangs"
 #    level_tab_thangs_all: "All"
 #    level_tab_thangs_conditions: "Starting Conditions"
diff --git a/app/locale/mk-MK.coffee b/app/locale/mk-MK.coffee
index cb1677819..8d22762ea 100644
--- a/app/locale/mk-MK.coffee
+++ b/app/locale/mk-MK.coffee
@@ -53,7 +53,7 @@ module.exports = nativeDescription: "Македонски", englishDescription:
     spectate: "Набљудувај" # Ladder page
     players: "играчи" # Hover over a level on /play
     hours_played: "изиграни часови" # Hover over a level on /play
-    items: "Предмети" # Tooltip on item shop button from /play
+    items: "Опрема" # Tooltip on item shop button from /play
     unlock: "Отклучи" # For purchasing items and heroes
     confirm: "Потврди"
     owned: "Имаш" # For items you own
@@ -64,7 +64,7 @@ module.exports = nativeDescription: "Македонски", englishDescription:
     heroes: "Херои" # Tooltip on hero shop button from /play
     achievements: "Постигнувања" # Tooltip on achievement list button from /play
     account: "Сметка" # Tooltip on account button from /play
-    settings: "Поставки" # Tooltip on settings button from /play
+    settings: "Подесувања" # Tooltip on settings button from /play
     next: "Следно" # Go from choose hero to choose inventory before playing a level
     change_hero: "Смени херој" # Go back from choose inventory to choose hero
     choose_inventory: "Опреми се"
@@ -102,7 +102,7 @@ module.exports = nativeDescription: "Македонски", englishDescription:
     log_in: "Најави се"
     logging_in: "Најавувањето е во тек"
     log_out: "Одјави се"
-    forgot_password: "Ја заборави својата лозинка?"
+    forgot_password: "Ја заборави твојата лозинка?"
     authenticate_gplus: "Провери G+ најава"
     load_profile: "Вчитај G+ профил"
     load_email: "Вчитај G+ e-mail"
@@ -287,9 +287,9 @@ module.exports = nativeDescription: "Македонски", englishDescription:
     tip_error_free: "Постојат два начина за пишување програми без грешки; само третиот работи. - Alan Perlis"
     tip_debugging_program: "Ако дебагирање е процесот на отстранување грешки, тогаш програмирање мора да е процесот при кој тие настануваат. - Edsger W. Dijkstra"
     tip_forums: "Упати се кон форумите и кажи ни што мислиш!"
-#    tip_baby_coders: "In the future, even babies will be Archmages."
+    tip_baby_coders: "Во иднината, дури и бебињата ќе бидат Веле-Волшебници."
     tip_morale_improves: "Вчитувањето ќе продолжи се додека моралот не се подобри."
-#    tip_all_species: "We believe in equal opportunities to learn programming for all species."
+    tip_all_species: "Веруваме во еднакви можности за изучување на програмирањето за сите видови."
 #    tip_reticulating: "Reticulating spines."
 #    tip_harry: "Yer a Wizard, "
     tip_great_responsibility: "Со големи програмерски вештини доаѓа и голема одговорност за дебагирање."
@@ -297,7 +297,7 @@ module.exports = nativeDescription: "Македонски", englishDescription:
     tip_binary: "Има само 10 вида на луѓе во светот: оние кои што разбираат бинарно, и оние кои не разбираат."
     tip_commitment_yoda: "Програмер мора да ја има најдлабоката обврзаност, најсериозниот ум. ~ Yoda"
     tip_no_try: "Направи. Или не прави. Нема обидување. - Yoda"
-#    tip_patience: "Patience you must have, young Padawan. - Yoda"
+    tip_patience: "Трпение мора да имаш, млад Padawan. - Yoda"
     tip_documented_bug: "Документирана грешка не е грешка; тоа е едноставно нешто што програмот го прави."
     tip_impossible: "Секогаш изледа невозможно, се додека некој не го направи. - Nelson Mandela"
     tip_talk_is_cheap: "Зборувањето е евтино. Покажи ми го кодот. - Linus Torvalds"
@@ -305,26 +305,26 @@ module.exports = nativeDescription: "Македонски", englishDescription:
     tip_hardware_problem: "Прашање: Колку програмери се потребни за да се смени сијалица?  Одговор: Ниту еден, тоа е хардверски проблем."
     tip_hofstadters_law: "Законот на Hofstadter: Секогаш треба повеќе време отколку што очекуваш, дури и кога ќе го земеш во предвид законот на Hofstadter."
     tip_premature_optimization: "Предвремената оптимизација е коренот на сето зло. - Donald Knuth"
-#    tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
+    tip_brute_force: "Кога не си сигурен, користи 'brute force'. - Ken Thompson"
     tip_extrapolation: "Има само два вида на луѓе: оние кои можат да екстраполираат од некомплетни податоци..."
     tip_superpower: "Програмирањето е способност, најблиска до супермоќ, која ја имаме."
 
-#  game_menu:
+  game_menu:
 #    inventory_tab: "Inventory"
-#    save_load_tab: "Save/Load"
+    save_load_tab: "Зачувај/Вчитај"
 #    options_tab: "Options"
-#    guide_tab: "Guide"
-#    guide_video_tutorial: "Video Tutorial"
-#    guide_tips: "Tips"
-#    multiplayer_tab: "Multiplayer"
-#    auth_tab: "Sign Up"
-#    inventory_caption: "Equip your hero"
-#    choose_hero_caption: "Choose hero, language"
-#    save_load_caption: "... and view history"
-#    options_caption: "Configure settings"
-#    guide_caption: "Docs and tips"
-#    multiplayer_caption: "Play with friends!"
-#    auth_caption: "Save your progress."
+    guide_tab: "Водич"
+    guide_video_tutorial: "Видео водич"
+    guide_tips: "Совети"
+    multiplayer_tab: "Повеќе играчи"
+    auth_tab: "Направи сметка"
+    inventory_caption: "Опреми го твојот херој"
+    choose_hero_caption: "Избери херој, јазик"
+    save_load_caption: "... и види историја"
+    options_caption: "Промени подесувања"
+    guide_caption: "Документи и совети"
+    multiplayer_caption: "Играј со пријатели!"
+    auth_caption: "Зачувај го твојот напредок."
 
   inventory:
 #    choose_inventory: "Equip Items"
@@ -349,70 +349,70 @@ module.exports = nativeDescription: "Македонски", englishDescription:
     prompt_title: "Немаш доволно скапоцени камења"
     prompt_body: "Дали сакаш да земеш повеќе?"
     prompt_button: "Влези во продавницата"
-#    recovered: "Previous gems purchase recovered. Please refresh the page."
+    recovered: "Претходното купување на скапоцени камења е вратено од загуба. Те молам 'освежи' ја страната."
 
-#  subscribe:
-#    subscribe_title: "Subscribe"
-#    unsubscribe: "Unsubscribe"
-#    levels: "Get more practice with bonus levels!"
-#    heroes: "More powerful heroes!"
-#    gems: "3500 bonus gems every month!"
-#    items: "Over 250 bonus items!"
-#    parents: "For Parents"
-#    parents_title: "Your child will learn to code."
-#    parents_blurb1: "With CodeCombat, your child learns by writing real code. They start by learning simple commands, and progress to more advanced topics."
-#    parents_blurb2: "For $9.99 USD/mo, they get new challenges every week and personal email support from professional programmers."
-#    parents_blurb3: "No Risk: 100% money back guarantee, easy 1-click unsubscribe."
-#    subscribe_button: "Subscribe Now"
-#    stripe_description: "Monthly Subscription"
-#    subscription_required_to_play: "You'll need a subscription to play this level."
+  subscribe:
+    subscribe_title: "Зачлени се"
+    unsubscribe: "Откажи членство"
+    levels: "Вежбај повеќе со дополнителни нивоа!"
+    heroes: "Помоќни херои!"
+    gems: "3500 скапоцени камења секој месец!"
+    items: "Над 250 дополнителни предмети и опрема!"
+    parents: "За родители"
+    parents_title: "Вашето дете ќе научи да програмира."
+    parents_blurb1: "Со CodeCombat, вашите деца учат преку пишување на вистински програмски код. Почнуваат со учење на едноставни команди, по што се продолжува на понапредни теми."
+    parents_blurb2: "За $9.99 американски долари месечно, добиваат нови предизвици секоја недела и лична поддршка преку e-mail, од страна на професионални програмери."
+    parents_blurb3: "Без ризик: 100% гаранција за враќање на парите, лесно откажување на членството со еден клик."
+    subscribe_button: "Зачлени се сега"
+    stripe_description: "Месечна членарина"
+    subscription_required_to_play: "Треба да бидеш зачленет за да го играш ова ниво."
 
-#  choose_hero:
-#    choose_hero: "Choose Your Hero"
-#    programming_language: "Programming Language"
-#    programming_language_description: "Which programming language do you want to use?"
+  choose_hero:
+    choose_hero: "Избери го твојот херој"
+    programming_language: "Програмски јазик"
+    programming_language_description: "Кој програмски јазик сакаш да го користиш?"
 #    default: "Default"
-#    experimental: "Experimental"
-#    python_blurb: "Simple yet powerful, great for beginners and experts."
-#    javascript_blurb: "The language of the web. (Not the same as Java.)"
-#    coffeescript_blurb: "Nicer JavaScript syntax."
-#    clojure_blurb: "A modern Lisp."
-#    lua_blurb: "Game scripting language."
-#    io_blurb: "Simple but obscure."
-#    status: "Status"
-#    weapons: "Weapons"
-#    weapons_warrior: "Swords - Short Range, No Magic"
-#    weapons_ranger: "Crossbows, Guns - Long Range, No Magic"
-#    weapons_wizard: "Wands, Staffs - Long Range, Magic"
-#    attack: "Damage" # Can also translate as "Attack"
-#    health: "Health"
-#    speed: "Speed"
-#    regeneration: "Regeneration"
-#    range: "Range" # As in "attack or visual range"
-#    blocks: "Blocks" # As in "this shield blocks this much damage"
-#    backstab: "Backstab" # As in "this dagger does this much backstab damage"
-#    skills: "Skills"
-#    available_for_purchase: "Available for Purchase" # Shows up when you have unlocked, but not purchased, a hero in the hero store
-#    level_to_unlock: "Level to unlock:" # Label for which level you have to beat to unlock a particular hero (click a locked hero in the store to see)
-#    restricted_to_certain_heroes: "Only certain heroes can play this level."
+    experimental: "Експериментално"
+    python_blurb: "Едноставен, но моќен, одличен за почетници и експерти."
+    javascript_blurb: "Јазикот на веб-от. (Не е исто што и Java.)"
+    coffeescript_blurb: "Поубава JavaScript синтакса."
+    clojure_blurb: "Модерен Lisp."
+    lua_blurb: "Јазик за скриптирање на игри."
+    io_blurb: "Едноставен, но не така очигледен."
+    status: "Статус"
+    weapons: "Оружја"
+    weapons_warrior: "Мечеви - Краток досег, нема магија"
+    weapons_ranger: "Самострели, Пушки - Долг досег, нема магија"
+    weapons_wizard: "Волшебни стапчиња, стапови - долг досег, има магија"
+    attack: "Напад" # Can also translate as "Attack"
+    health: "Здравје"
+    speed: "Брзина"
+    regeneration: "Заздравување"
+    range: "Досег" # As in "attack or visual range"
+    blocks: "Блокира" # As in "this shield blocks this much damage"
+    backstab: "Од зад грб" # As in "this dagger does this much backstab damage"
+    skills: "Вештини"
+    available_for_purchase: "Достапно за купување" # Shows up when you have unlocked, but not purchased, a hero in the hero store
+    level_to_unlock: "Ниво за да се отклучи:" # Label for which level you have to beat to unlock a particular hero (click a locked hero in the store to see)
+    restricted_to_certain_heroes: "Само одредени херои можат да го играат ова ниво."
 
-#  skill_docs:
+  skill_docs:
 #    writable: "writable" # Hover over "attack" in Your Skills while playing a level to see most of this
 #    read_only: "read-only"
-#    action_name: "name"
+    action_name: "име"
 #    action_cooldown: "Takes"
 #    action_specific_cooldown: "Cooldown"
 #    action_damage: "Damage"
-#    action_range: "Range"
-#    action_radius: "Radius"
-#    action_duration: "Duration"
-#    example: "Example"
-#    ex: "ex" # Abbreviation of "example"
-#    current_value: "Current Value"
-#    default_value: "Default value"
-#    parameters: "Parameters"
-#    returns: "Returns"
-#    granted_by: "Granted by"
+    action_range: "Досег"
+    action_radius: "Радиус"
+    action_duration: "Времетраење"
+    example: "Пример"
+    ex: "пр" # Abbreviation of "example"
+    current_value: "Моментална вредност"
+    default_value: "Стандардна вредност"
+    parameters: "Параметри"
+    returns: "Враќа"
+    granted_by: "Овозможено од"
 
 #  save_load:
 #    granularity_saved_games: "Saved"
diff --git a/app/locale/nl-NL.coffee b/app/locale/nl-NL.coffee
index effa5f76e..b41626252 100644
--- a/app/locale/nl-NL.coffee
+++ b/app/locale/nl-NL.coffee
@@ -4,7 +4,7 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
     no_ie: "CodeCombat werkt niet in IE8 of ouder. Sorry!" # Warning that only shows up in IE8 and older
     no_mobile: "CodeCombat is niet gemaakt voor mobiele apparaten en werkt misschien niet!" # Warning that shows up on mobile devices
     play: "Speel" # The big play button that just starts playing a level
-#    try_it: "Try It" # Alternate wording for Play button
+    try_it: "Probeer nu" # Alternate wording for Play button
     old_browser: "Uh oh, jouw browser is te oud om CodeCombat te kunnen spelen, Sorry!" # Warning that shows up on really old Firefox/Chrome/Safari
     old_browser_suffix: "Je kan toch proberen, maar het zal waarschijnlijk niet werken!"
     ipad_browser: "Slecht nieuws: CodeCombat draait niet in je browser op iPad. Goed nieuws: onze iPad-app wordt op het moment beoordeeld door Apple."
@@ -22,7 +22,7 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
     forum: "Forum"
     account: "Lidmaatschap"
     profile: "Profiel"
-#    stats: "Stats"
+    stats: "Statistieken"
 #    code: "Code"
     admin: "Administrator" # Only shows up when you are an admin
     home: "Home"
@@ -31,7 +31,7 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
     about: "Over Ons"
     contact: "Contact"
     twitter_follow: "Volgen"
-#    teachers: "Teachers"
+    teachers: "Docenten"
 
   modal:
     close: "Sluiten"
@@ -52,36 +52,36 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
     play_as: "Speel als " # Ladder page
     spectate: "Toeschouwen" # Ladder page
     players: "Spelers" # Hover over a level on /play
-#    hours_played: "hours played" # Hover over a level on /play
+    hours_played: "Speeltijd" # Hover over a level on /play
 #    items: "Items" # Tooltip on item shop button from /play
-    unlock: "Ontgrendel" # For purchasing items and heroes
+    unlock: "Koop" # For purchasing items and heroes
     confirm: "Bevestigen"
-#    owned: "Owned" # For items you own
+    owned: "In bezit" # For items you own
     locked: "Vergrendeld"
-#    purchasable: "Purchasable" # For a hero you unlocked but haven't purchased
-#    available: "Available"
+    purchasable: "Te koop" # For a hero you unlocked but haven't purchased
+    available: "Beschikbaar"
 #    skills_granted: "Skills Granted" # Property documentation details
-#    heroes: "Heroes" # Tooltip on hero shop button from /play
-#    achievements: "Achievements" # Tooltip on achievement list button from /play
+    heroes: "Helden" # Tooltip on hero shop button from /play
+    achievements: "Prestaties" # Tooltip on achievement list button from /play
 #    account: "Account" # Tooltip on account button from /play
     settings: "Instellingen" # Tooltip on settings button from /play
-#    next: "Next" # Go from choose hero to choose inventory before playing a level
-#    change_hero: "Change Hero" # Go back from choose inventory to choose hero
+    next: "Volgende" # Go from choose hero to choose inventory before playing a level
+    change_hero: "Verander held" # Go back from choose inventory to choose hero
 #    choose_inventory: "Equip Items"
     buy_gems: "Edelstenen kopen"
-#    campaign_desert: "Desert Campaign"
-#    campaign_forest: "Forest Campaign"
-#    campaign_dungeon: "Dungeon Campaign"
-#    subscription_required: "Subscription Required"
+    campaign_desert: "Woestijncampagne"
+    campaign_forest: "Boscampagne"
+    campaign_dungeon: "Kerkercampagne"
+    subscription_required: "Abonnement nodig"
     free: "Gratis"
     subscribed: "Geabbonneerd"
 #    older_campaigns: "Older Campaigns"
     anonymous: "Anonieme Speler"
     level_difficulty: "Moeilijkheidsgraad: "
     campaign_beginner: "Beginnercampagne"
-#    awaiting_levels_adventurer_prefix: "We release five levels per week."
-#    awaiting_levels_adventurer: "Sign up as an Adventurer"
-#    awaiting_levels_adventurer_suffix: "to be the first to play new levels."
+    awaiting_levels_adventurer_prefix: "We brengen 5 nieuwe levels per week uit."
+    awaiting_levels_adventurer: "Schrijf je in als Avonturier"
+    awaiting_levels_adventurer_suffix: "om de eerste te zijn die nieuwe levels speelt."
     choose_your_level: "Kies Je Level" # The rest of this section is the old play view at /play-old and isn't very important.
     adventurer_prefix: "Je kunt meteen naar een van de levels hieronder springen, of de levels bespreken op "
     adventurer_forum: "het Avonturiersforum"
@@ -106,7 +106,7 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
     authenticate_gplus: "G+ verifiëren"
     load_profile: "G+ profiel laden"
     load_email: "G+ e-mail laden"
-#    finishing: "Finishing"
+    finishing: "Aan het afmaken"
     sign_in_with_facebook: "Inloggen met Facebook"
     sign_in_with_gplus: "Inloggen met G+"
     signup_switch: "Wil je een account maken?"
@@ -117,7 +117,7 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
     sign_up: "Aanmelden"
     log_in: "inloggen met wachtwoord"
     social_signup: "Of je kunt je registreren met Facebook of G+:"
-#    required: "You need to log in before you can go that way."
+    required: "Je moet inloggen om daarheen te gaan."
     login_switch: "Heb je al een account?"
 
   recover:
@@ -125,13 +125,13 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
     send_password: "Verzend nieuw wachtwoord"
     recovery_sent: "Herstel e-mail verzonden."
 
-#  items:
-#    primary: "Primary"
-#    secondary: "Secondary"
-#    armor: "Armor"
-#    accessories: "Accessories"
+  items:
+    primary: "Primair"
+    secondary: "Secundair"
+    armor: "Harnas"
+    accessories: "Accessoires"
 #    misc: "Misc"
-#    books: "Books"
+    books: "Boeken"
 
   common:
     loading: "Bezig met laden..."
@@ -146,18 +146,18 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
     fork: "Fork"
     play: "Spelen" # When used as an action verb, like "Play next level"
     retry: "Probeer opnieuw"
-#    actions: "Actions"
+    actions: "Acties"
 #    info: "Info"
 #    help: "Help"
     watch: "Volgen"
     unwatch: "Ontvolgen"
     submit_patch: "Correctie Opsturen"
-#    submit_changes: "Submit Changes"
+    submit_changes: "Veranderingen indienen"
 
   general:
     and: "en"
     name: "Naam"
-#    date: "Date"
+    date: "Datum"
     body: "Inhoud"
     version: "Versie"
 #    submitter: "Submitter"
@@ -166,10 +166,10 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
 #    review: "Review"
     version_history: "Versie geschiedenis"
     version_history_for: "Versie geschiedenis voor: "
-#    select_changes: "Select two changes below to see the difference."
+    select_changes: "Selecteer hieronder twee veranderingen om het verschil te zien."
 #    undo: "Undo (Ctrl+Z)"
 #    redo: "Redo (Ctrl+Shift+Z)"
-#    play_preview: "Play preview of current level"
+    play_preview: "Speel voorproefje van dit level"
     result: "Resultaat"
     results: "Resultaten"
     description: "Beschrijving"
@@ -191,7 +191,7 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
     medium: "Medium"
     hard: "Moeilijk"
     player: "Speler"
-#    player_level: "Level" # Like player level 5, not like level: Dungeons of Kithgard
+    player_level: "Niveau" # Like player level 5, not like level: Dungeons of Kithgard
 
   units:
     second: "seconde"
@@ -200,53 +200,53 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
     minutes: "minuten"
     hour: "uur"
     hours: "uren"
-#    day: "day"
-#    days: "days"
-#    week: "week"
-#    weeks: "weeks"
-#    month: "month"
-#    months: "months"
-#    year: "year"
-#    years: "years"
+    day: "dag"
+    days: "dagen"
+    week: "week"
+    weeks: "weken"
+    month: "maand"
+    months: "maanden"
+    year: "jaar"
+    years: "jaren"
 
   play_level:
     done: "Klaar"
     home: "Home" # Not used any more, will be removed soon.
 #    level: "Level" # Like "Level: Dungeons of Kithgard"
-#    skip: "Skip"
+    skip: "Overslaan"
 #    game_menu: "Game Menu"
     guide: "Handleiding"
     restart: "Herstarten"
     goals: "Doelen"
-#    goal: "Goal"
+    goal: "Doel"
 #    running: "Running..."
-#    success: "Success!"
-#    incomplete: "Incomplete"
-#    timed_out: "Ran out of time"
+    success: "Gelukt!"
+    incomplete: "Incompleet"
+    timed_out: "De tijd is op"
 #    failing: "Failing"
     action_timeline: "Actie tijdlijn"
     click_to_select: "Klik op een eenheid om deze te selecteren."
 #    control_bar_multiplayer: "Multiplayer"
-#    control_bar_join_game: "Join Game"
-#    reload: "Reload"
+    control_bar_join_game: "Meespelen"
+    reload: "Herlaad"
     reload_title: "Alle Code Herladen?"
     reload_really: "Weet je zeker dat je dit level tot het begin wilt herladen?"
     reload_confirm: "Herlaad Alles"
-#    victory: "Victory"
+    victory: "Gewonnen"
     victory_title_prefix: ""
     victory_title_suffix: " Compleet"
     victory_sign_up: "Schrijf je in om je vooruitgang op te slaan"
     victory_sign_up_poke: "Wil je jouw code opslaan? Maak een gratis account aan!"
     victory_rate_the_level: "Beoordeel het level: " # Only in old-style levels.
     victory_return_to_ladder: "Keer terug naar de ladder"
-#    victory_play_continue: "Continue"
-#    victory_saving_progress: "Saving Progress"
+    victory_play_continue: "Ga door"
+    victory_saving_progress: "Voortgang opslaan"
     victory_go_home: "Ga naar Home" # Only in old-style levels.
     victory_review: "Vertel ons meer!" # Only in old-style levels.
     victory_hour_of_code_done: "Ben Je Klaar?"
     victory_hour_of_code_done_yes: "Ja, ik ben klaar met mijn Hour of Code!"
-#    victory_experience_gained: "XP Gained"
-#    victory_gems_gained: "Gems Gained"
+    victory_experience_gained: "XP verdient"
+    victory_gems_gained: "Edelstenen verdient"
     guide_title: "Handleiding"
     tome_minion_spells: "Jouw Minions' Spreuken" # Only in old-style levels.
     tome_read_only_spells: "Read-Only Spreuken" # Only in old-style levels.
diff --git a/app/locale/zh-HANS.coffee b/app/locale/zh-HANS.coffee
index f7d491f9b..7cb7e59ad 100644
--- a/app/locale/zh-HANS.coffee
+++ b/app/locale/zh-HANS.coffee
@@ -102,7 +102,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
     log_in: "登录"
     logging_in: "正在登录"
     log_out: "登出"
-#    forgot_password: "Forgot your password?"
+    forgot_password: "忘记密码?"
     authenticate_gplus: "使用 G+ 授权"
     load_profile: "载入 G+ 档案"
     load_email: "载入 G+ 电子邮件"
@@ -148,7 +148,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
     retry: "重试"
 #    actions: "Actions"
 #    info: "Info"
-#    help: "Help"
+    help: "帮助"
     watch: "关注"
     unwatch: "取消关注"
     submit_patch: "提交补丁"
@@ -353,7 +353,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
 
   subscribe:
     subscribe_title: "订阅"
-#    unsubscribe: "Unsubscribe"
+    unsubscribe: "取消订阅"
     levels: "多解锁17个关卡!每周解锁5个新关卡!"
     heroes: "更多强大的英雄!"
     gems: "每月多3500宝石奖励!"
@@ -483,7 +483,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
     forum_page: "我们的论坛"
     forum_suffix: ""
 #    faq_prefix: "There's also a"
-#    faq: "FAQ"
+    faq: "FAQ"
 #    subscribe_prefix: "If you need help figuring out a level, please"
 #    subscribe: "buy a CodeCombat subscription"
 #    subscribe_suffix: "and we'll be happy to help you with your code."
diff --git a/app/models/CocoModel.coffee b/app/models/CocoModel.coffee
index e0e1be276..8f7bb6d16 100644
--- a/app/models/CocoModel.coffee
+++ b/app/models/CocoModel.coffee
@@ -94,7 +94,7 @@ class CocoModel extends Backbone.Model
 
   loadFromBackup: ->
     return unless @saveBackups
-    existing = storage.load @id
+    existing = storage.load @id  # + @attributes.__v  # TODO: try and test this, also only use __v for non-versioned, otherwise just id
     if existing
       @set(existing, {silent: true})
       CocoModel.backedUp[@id] = @
@@ -102,8 +102,8 @@ class CocoModel extends Backbone.Model
   saveBackup: -> @saveBackupNow()
 
   saveBackupNow: ->
-    storage.save(@id, @attributes)
-    CocoModel.backedUp[@id] = @
+    storage.save(@id, @attributes)  # TODO: use __v
+    CocoModel.backedUp[@id] = @   # TODO
 
   @backedUp = {}
   schema: -> return @constructor.schema
@@ -377,22 +377,37 @@ class CocoModel extends Backbone.Model
   #- Internationalization
 
   updateI18NCoverage: ->
-    i18nObjects = @findI18NObjects()
-    return unless i18nObjects.length
-    langCodeArrays = (_.keys(i18n) for i18n in i18nObjects)
-    @set('i18nCoverage', _.intersection(langCodeArrays...))
+    langCodeArrays = []
+    pathToData = {}
 
-  findI18NObjects: (data, results) ->
-    data ?= @attributes
-    results ?= []
+    TreemaUtils.walk(@attributes, @schema(), null, (path, data, workingSchema) ->
+      # Store parent data for the next block...
+      if data?.i18n
+        pathToData[path] = data
+        
+      if _.string.endsWith path, 'i18n'
+        i18n = data
+        
+        # grab the parent data
+        parentPath = path[0...-5]
+        parentData = pathToData[parentPath]
+        
+        # use it to determine what properties actually need to be translated
+        props = workingSchema.props or []
+        props = (prop for prop in props when parentData[prop])
+        
+        # get a list of lang codes where its object has keys for every prop to be translated
+        coverage = _.filter(_.keys(i18n), (langCode) ->
+          translations = i18n[langCode]
+          _.all((translations[prop] for prop in props))
+        )
+        langCodeArrays.push coverage
+    )
+    
+    return unless langCodeArrays.length
+    # language codes that are covered for every i18n object are fully covered
+    overallCoverage = _.intersection(langCodeArrays...)
+    @set('i18nCoverage', overallCoverage)
 
-    if _.isPlainObject(data) or _.isArray(data)
-      for [key, value] in _.pairs data
-        if key is 'i18n'
-          results.push value
-        else if _.isPlainObject(value) or _.isArray(value)
-          @findI18NObjects(value, results)
-
-    return results
 
 module.exports = CocoModel
diff --git a/app/models/LevelSession.coffee b/app/models/LevelSession.coffee
index 5d59d1bc2..cc30de4b8 100644
--- a/app/models/LevelSession.coffee
+++ b/app/models/LevelSession.coffee
@@ -53,3 +53,16 @@ module.exports = class LevelSession extends CocoModel
   save: (attrs, options) ->
     return if @shouldAvoidCorruptData attrs
     super attrs, options
+
+  increaseDifficulty: ->
+    state = @get('state') ? {}
+    state.difficulty = (state.difficulty ? 0) + 1
+    delete state.lastUnsuccessfulSubmissionTime
+    @set 'state', state
+
+  timeUntilResubmit: ->
+    state = @get('state') ? {}
+    return 0 unless last = state.lastUnsuccessfulSubmissionTime
+    last = new Date(last) if _.isString last
+    # Wait at least this long before allowing submit button active again.
+    (last - new Date()) + 22 * 60 * 60 * 1000
diff --git a/app/schemas/models/level.coffee b/app/schemas/models/level.coffee
index 3a4c7988b..581fb4104 100644
--- a/app/schemas/models/level.coffee
+++ b/app/schemas/models/level.coffee
@@ -299,6 +299,7 @@ _.extend LevelSchema.properties,
     style: c.shortString title: 'Style', description: 'Like: original, eccentric, scripted, edited, etc.'
     free: {type: 'boolean', title: 'Free', description: 'Whether this video is freely available to all players without a subscription.'}
     url: c.url {title: 'URL', description: 'Link to the video on Vimeo.'}
+  replayable: {type: 'boolean', title: 'Replayable', description: 'Whether this (hero) level infinitely scales up its difficulty and can be beaten over and over for greater rewards.'}
 
   # Admin flags
   adventurer: { type: 'boolean' }
diff --git a/app/schemas/models/level_session.coffee b/app/schemas/models/level_session.coffee
index 0c225906e..939d015ae 100644
--- a/app/schemas/models/level_session.coffee
+++ b/app/schemas/models/level_session.coffee
@@ -114,6 +114,12 @@ _.extend LevelSessionSchema.properties,
       description: 'How many times the session has been submitted for real-time playback (can affect the random seed).'
       type: 'integer'
       minimum: 0
+    difficulty:
+      description: 'The highest difficulty level beaten, for use in increasing-difficulty replayable levels.'
+      type: 'integer'
+      minimum: 0
+    lastUnsuccessfulSubmissionTime: c.date
+      description: 'The last time that real-time submission was started without resulting in a win.'
     flagHistory:
       description: 'The history of flag events during the last real-time playback submission.'
       type: 'array'
diff --git a/app/schemas/subscriptions/tome.coffee b/app/schemas/subscriptions/tome.coffee
index 7cdcef7a0..6dd258cf4 100644
--- a/app/schemas/subscriptions/tome.coffee
+++ b/app/schemas/subscriptions/tome.coffee
@@ -7,16 +7,20 @@ module.exports =
     preload: {type: 'boolean'}
     realTime: {type: 'boolean'}
 
-  'tome:cast-spells': c.object {title: 'Cast Spells', description: 'Published when spells are cast', required: ['spells', 'preload', 'realTime', 'submissionCount', 'flagHistory']},
+  'tome:cast-spells': c.object {title: 'Cast Spells', description: 'Published when spells are cast', required: ['spells', 'preload', 'realTime', 'submissionCount', 'flagHistory', 'difficulty']},
     spells: [type: 'object']
     preload: [type: 'boolean']
     realTime: [type: 'boolean']
     submissionCount: [type: 'integer']
     flagHistory: [type: 'array']
+    difficulty: [type: 'integer']
 
   'tome:manual-cast': c.object {title: 'Manually Cast Spells', description: 'Published when you wish to manually recast all spells', required: []},
     realTime: {type: 'boolean'}
 
+  'tome:manual-cast-denied': c.object {title: 'Manual Cast Denied', description: 'Published when player attempts to submit for real-time playback, but must wait after a replayable level failure.', required: ['timeUntilResubmit']},
+    timeUntilResubmit: {type: 'number'}
+
   'tome:spell-created': c.object {title: 'Spell Created', description: 'Published after a new spell has been created', required: ['spell']},
     spell: {type: 'object'}
 
diff --git a/app/styles/editor/campaign/campaign-editor-view.sass b/app/styles/editor/campaign/campaign-editor-view.sass
index fd133e581..f96fffc5a 100644
--- a/app/styles/editor/campaign/campaign-editor-view.sass
+++ b/app/styles/editor/campaign/campaign-editor-view.sass
@@ -28,5 +28,8 @@
       top: 1%
       padding: 3px 8px
 
-    #analytics-modal .modal-content
-      background-color: white
+    #analytics-modal 
+      .modal-dialog
+        width: 75%
+      .modal-content
+        background-color: white
diff --git a/app/styles/editor/editor.sass b/app/styles/editor/editor.sass
index 34eccdf79..1314f92c3 100644
--- a/app/styles/editor/editor.sass
+++ b/app/styles/editor/editor.sass
@@ -137,6 +137,8 @@
   .treema-root
     background-color: white
     border-radius: 4px
+    &:focus
+      box-shadow: 0 0 10px blue
 
   .editor-nano-container
     position: static
diff --git a/app/styles/editor/level/thangs-tab-view.sass b/app/styles/editor/level/thangs-tab-view.sass
index 84dd8b2c2..60fbded75 100644
--- a/app/styles/editor/level/thangs-tab-view.sass
+++ b/app/styles/editor/level/thangs-tab-view.sass
@@ -125,3 +125,8 @@
       
     #contextmenu
       text-align: left
+
+  #thang-components-edit-view
+    #thang-component-configs
+      // Get these away from the bottom of the screen so we can have dropdowns.
+      padding-bottom: 400px
diff --git a/app/templates/editor/campaign/campaign-editor-view.jade b/app/templates/editor/campaign/campaign-editor-view.jade
index c6c40f058..d48490b9b 100644
--- a/app/templates/editor/campaign/campaign-editor-view.jade
+++ b/app/templates/editor/campaign/campaign-editor-view.jade
@@ -65,6 +65,7 @@ block outer_content
                       td Finished
                       td Dropped
                       td Drop %
+                      td Completion %
                   tbody
                     - for (var i = 0; i < campaignDropOffs.levels.length; i++)
                       tr
@@ -75,6 +76,7 @@ block outer_content
                         td= campaignDropOffs.levels[i].finished
                         td= campaignDropOffs.levels[i].finishDropped
                         td= campaignDropOffs.levels[i].finishDropRate
+                        td= campaignDropOffs.levels[i].completionRate
       else
         button.btn.btn-default.disabled#analytics-button Analytics Loading...
 
diff --git a/app/templates/editor/campaign/campaign-level-view.jade b/app/templates/editor/campaign/campaign-level-view.jade
index ecdcd84b5..d991752af 100644
--- a/app/templates/editor/campaign/campaign-level-view.jade
+++ b/app/templates/editor/campaign/campaign-level-view.jade
@@ -38,9 +38,59 @@
             td= levelPlaytimes[i].average.toFixed(2)
   else
     div Loading...
-                
-                
-                
+
+  h4 Common Problems
+  if commonProblems
+    if commonProblems.startDay
+      if commonProblems.endDay
+        div(style='font-size:10pt') #{commonProblems.startDay} to #{commonProblems.endDay}
+      else
+        div(style='font-size:10pt') #{commonProblems.startDay} to today
+    table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt')
+      thead
+        tr
+          td Language
+          td Error Message
+          td Error Hint
+          td Count
+      tbody
+        - for (var i = 0; i < commonProblems.length && i < 20; i++)
+          tr
+            td= commonProblems[i].language
+            td= commonProblems[i].message
+            td= commonProblems[i].hint
+            td= commonProblems[i].count
+  else
+    div Loading...
+
+  h4 Recent Sessions
+  if recentSessions
+    div(style='font-size:10pt') Latest 10 sessions for this level
+    div(style='font-size:10pt') Double-click row to open player and session
+    table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt')
+      thead
+        tr
+          td Session ID
+          td Player ID
+          td Code Language
+          td Playtime
+          td Complete
+          td Changed
+      tbody
+        - for (var i = 0; i < recentSessions.length; i++)
+          tr.recent-session(data-player-id=recentSessions[i].creator, data-session-id=recentSessions[i]._id)
+            td= recentSessions[i]._id
+            td= recentSessions[i].creator
+            td= recentSessions[i].codeLanguage
+            td= recentSessions[i].playtime
+            if recentSessions[i].state && recentSessions[i].state.complete
+              td= recentSessions[i].state.complete
+            else
+              td false
+            td= recentSessions[i].changed
+  else
+    div Loading...
+
 if level.get('tasks')
   .tasks
     h3 Tasks (read only)
diff --git a/app/templates/editor/level/component/level-component-edit-view.jade b/app/templates/editor/level/component/level-component-edit-view.jade
index 65f3635be..a98cc6db0 100644
--- a/app/templates/editor/level/component/level-component-edit-view.jade
+++ b/app/templates/editor/level/component/level-component-edit-view.jade
@@ -33,7 +33,7 @@ nav.navbar.navbar-default(role='navigation')
               span.spl(data-i18n="common.unwatch") Unwatch
         if !me.get('anonymous')
           li#create-new-component-button
-            a(data-i18n="editor.level_component_b_new") Create New Component
+            a(data-i18n="editor.level_component_btn_new") Create New Component
           li
             a(data-i18n="editor.pop_i18n")#pop-component-i18n-button Populate i18n
 
diff --git a/app/templates/editor/level/edit.jade b/app/templates/editor/level/edit.jade
index f1e3999d5..f12e3df08 100644
--- a/app/templates/editor/level/edit.jade
+++ b/app/templates/editor/level/edit.jade
@@ -44,10 +44,10 @@ block header
         span.navbar-brand #{level.attributes.name}
 
       ul.nav.navbar-nav.navbar-right
-        li#undo-button(data-i18n="[title]general.undo", title="Undo (Ctrl+Z)")
+        li#undo-button
           a
             span.glyphicon-arrow-left.glyphicon
-        li#redo-button(data-i18n="[title]general.redo", title="Redo (Ctrl+Shift+Z)")
+        li#redo-button
             a
               span.glyphicon-arrow-right.glyphicon
         if authorized
diff --git a/app/templates/play/level/tome/cast_button.jade b/app/templates/play/level/tome/cast_button.jade
index 77886e0df..7527f9ba6 100644
--- a/app/templates/play/level/tome/cast_button.jade
+++ b/app/templates/play/level/tome/cast_button.jade
@@ -1,7 +1,9 @@
 button.btn.btn-lg.btn-illustrated.cast-button(title=castVerbose)
   span(data-i18n="play_level.tome_run_button_ran") Ran
 
-button.btn.btn-lg.btn-illustrated.submit-button(title=castRealTimeVerbose, data-i18n="play_level.tome_submit_button") Submit
+button.btn.btn-lg.btn-illustrated.submit-button(title=castRealTimeVerbose)
+  span(data-i18n="play_level.tome_submit_button") Submit
+  span.spl.secret.submit-again-time
 
 button.btn.btn-lg.btn-illustrated.done-button.secret
   span(data-i18n="play_level.done") Done
diff --git a/app/views/editor/achievement/AchievementEditView.coffee b/app/views/editor/achievement/AchievementEditView.coffee
index 14c28a2be..8fbe6abf5 100644
--- a/app/views/editor/achievement/AchievementEditView.coffee
+++ b/app/views/editor/achievement/AchievementEditView.coffee
@@ -63,7 +63,7 @@ module.exports = class AchievementEditView extends RootView
     @$el.find('#achievement-view').empty()
     for key, value of @treema.data
       @achievement.set key, value
-    earned = earnedPoints: @achievement.get 'worth'
+    earned = get: (key) => {earnedPoints: @achievement.get('worth'), previouslyAchievedAmount: 0}[key]
     popup = new AchievementPopup achievement: @achievement, earnedAchievement: earned, popup: false, container: $('#achievement-view')
 
   openSaveModal: ->
diff --git a/app/views/editor/campaign/CampaignEditorView.coffee b/app/views/editor/campaign/CampaignEditorView.coffee
index 51598815e..71f434fb2 100644
--- a/app/views/editor/campaign/CampaignEditorView.coffee
+++ b/app/views/editor/campaign/CampaignEditorView.coffee
@@ -47,7 +47,7 @@ module.exports = class CampaignEditorView extends RootView
     @listenToOnce @levels, 'sync', @onFundamentalLoaded
     @listenToOnce @achievements, 'sync', @onFundamentalLoaded
 
-    _.delay @getCampaignDropOffs, 1000
+    _.delay @getCampaignCompletions, 1000
 
   loadThangTypeNames: ->
     # Load the names of the ThangTypes that this level's Treema nodes might want to display.
@@ -240,7 +240,7 @@ module.exports = class CampaignEditorView extends RootView
       if achievement.hasLocalChanges()
         @toSave.add achievement
 
-  getCampaignDropOffs: =>
+  getCampaignCompletions: =>
     # Fetch last 7 days of campaign drop-off rates
 
     startDay = new Date()
@@ -254,14 +254,15 @@ module.exports = class CampaignEditorView extends RootView
       mapFn = (item) ->
         item.startDropRate = (item.startDropped / item.started * 100).toFixed(2)
         item.finishDropRate = (item.finishDropped / item.finished * 100).toFixed(2)
+        item.completionRate = (item.finished / item.started * 100).toFixed(2)
         item
       @campaignDropOffs.levels = _.map @campaignDropOffs.levels, mapFn, @
       @campaignDropOffs.startDay = startDay
       @render()
 
     # TODO: Why do we need this url dash?
-    request = @supermodel.addRequestResource 'campaign_drop_offs', {
-      url: '/db/analytics_log_event/-/campaign_drop_offs'
+    request = @supermodel.addRequestResource 'campaign_completions', {
+      url: '/db/analytics_log_event/-/campaign_completions'
       data: {startDay: startDay, slug: @campaignHandle}
       method: 'POST'
       success: success
diff --git a/app/views/editor/campaign/CampaignLevelView.coffee b/app/views/editor/campaign/CampaignLevelView.coffee
index 56e3df97e..3771520d5 100644
--- a/app/views/editor/campaign/CampaignLevelView.coffee
+++ b/app/views/editor/campaign/CampaignLevelView.coffee
@@ -1,5 +1,8 @@
 CocoView = require 'views/core/CocoView'
 Level = require 'models/Level'
+LevelSession = require 'models/LevelSession'
+ModelModal = require 'views/modal/ModelModal'
+User = require 'models/User'
 
 module.exports = class CampaignLevelView extends CocoView
   id: 'campaign-level-view'
@@ -7,6 +10,7 @@ module.exports = class CampaignLevelView extends CocoView
 
   events:
     'click .close': 'onClickClose'
+    'dblclick .recent-session': 'onDblClickRecentSession'
 
   constructor: (options, @level) ->
     super(options)
@@ -15,20 +19,53 @@ module.exports = class CampaignLevelView extends CocoView
     @listenToOnce @fullLevel, 'sync', => @render?()
 
     @levelSlug = @level.get('slug')
+    @getCommonLevelProblems()
     @getLevelCompletions()
     @getLevelPlaytimes()
+    @getRecentSessions()
 
   getRenderData: ->
     c = super()
     c.level = if @fullLevel.loaded then @fullLevel else @level
+    c.commonProblems = @commonProblems
     c.levelCompletions = @levelCompletions
     c.levelPlaytimes = @levelPlaytimes
+    c.recentSessions = @recentSessions
     c
 
   onClickClose: ->
     @$el.addClass('hidden')
     @trigger 'hidden'
 
+  onDblClickRecentSession: (e) ->
+    # Admin view of players' code
+    return unless me.isAdmin()
+    row = $(e.target).parent()
+    player = new User _id: row.data 'player-id'
+    session = new LevelSession _id: row.data 'session-id'
+    @openModalView new ModelModal models: [session, player]
+
+  getCommonLevelProblems: ->
+    # Fetch last 30 days of common level problems
+    startDay = new Date()
+    startDay.setDate(startDay.getUTCDate() - 29)
+    startDay = startDay.getUTCFullYear() + '-' + (startDay.getUTCMonth() + 1) + '-' + startDay.getUTCDate()
+
+    success = (data) =>
+      return if @destroyed
+      @commonProblems = data
+      @commonProblems.startDay = startDay
+      @render()
+
+    # TODO: Why do we need this url dash?
+    request = @supermodel.addRequestResource 'common_problems', {
+      url: '/db/user_code_problem/-/common_problems'
+      data: {startDay: startDay, slug: @levelSlug}
+      method: 'POST'
+      success: success
+    }, 0
+    request.load()
+
   getLevelCompletions: ->
     # Fetch last 7 days of level completion counts
     success = (data) =>
@@ -72,3 +109,20 @@ module.exports = class CampaignLevelView extends CocoView
       success: success
     }, 0
     request.load()
+
+  getRecentSessions: ->
+    limit = 10
+
+    success = (data) =>
+      return if @destroyed
+      @recentSessions = data
+      @render()
+
+    # TODO: Why do we need this url dash?
+    request = @supermodel.addRequestResource 'level_sessions_recent', {
+      url: "/db/level_session/-/recent"
+      data: {slug: @levelSlug, limit: limit}
+      method: 'POST'
+      success: success
+    }, 0
+    request.load()
\ No newline at end of file
diff --git a/app/views/editor/level/LevelEditView.coffee b/app/views/editor/level/LevelEditView.coffee
index 53dabaa61..ff7892796 100644
--- a/app/views/editor/level/LevelEditView.coffee
+++ b/app/views/editor/level/LevelEditView.coffee
@@ -137,11 +137,11 @@ module.exports = class LevelEditView extends RootView
 
   showUndoDescription: ->
     undoDescription = TreemaNode.getLastTreemaWithFocus().getUndoDescription()
-    @$el.find('#undo-button').attr('title', 'Undo ' + undoDescription + ' (Ctrl+Z)')
+    @$el.find('#undo-button').attr('title', $.i18n.t("general.undo_prefix") + " " + undoDescription + " " + $.i18n.t("general.undo_shortcut"))
 
   showRedoDescription: ->
     redoDescription = TreemaNode.getLastTreemaWithFocus().getRedoDescription()
-    @$el.find('#redo-button').attr('title', 'Redo ' + redoDescription + ' (Ctrl+Shift+Z)')
+    @$el.find('#redo-button').attr('title', $.i18n.t("general.redo_prefix") + " " + redoDescription + " " + $.i18n.t("general.redo_shortcut"))
 
   getCurrentView: ->
     currentViewID = @$el.find('.tab-pane.active').attr('id')
diff --git a/app/views/editor/level/settings/SettingsTabView.coffee b/app/views/editor/level/settings/SettingsTabView.coffee
index fa59651f0..ff5cd6eb4 100644
--- a/app/views/editor/level/settings/SettingsTabView.coffee
+++ b/app/views/editor/level/settings/SettingsTabView.coffee
@@ -15,7 +15,7 @@ module.exports = class SettingsTabView extends CocoView
   editableSettings: [
     'name', 'description', 'documentation', 'nextLevel', 'background', 'victory', 'i18n', 'icon', 'goals',
     'type', 'terrain', 'showsGuide', 'banner', 'employerDescription', 'loadingTip', 'requiresSubscription',
-    'tasks', 'helpVideos'
+    'tasks', 'helpVideos', 'replayable'
   ]
 
   subscriptions:
diff --git a/app/views/play/level/PlayLevelView.coffee b/app/views/play/level/PlayLevelView.coffee
index 02e1e9de9..0d920f146 100644
--- a/app/views/play/level/PlayLevelView.coffee
+++ b/app/views/play/level/PlayLevelView.coffee
@@ -520,6 +520,7 @@ module.exports = class PlayLevelView extends RootView
     return if @destroyed
     # TODO: Show a victory dialog specific to hero-ladder level
     if @goalManager.checkOverallStatus() is 'success' and not @options.realTimeMultiplayerSessionID?
+      @session.increaseDifficulty() if @level.get 'replayable'
       Backbone.Mediator.publish 'level:show-victory', showModal: true
 
   destroy: ->
diff --git a/app/views/play/level/tome/CastButtonView.coffee b/app/views/play/level/tome/CastButtonView.coffee
index 8b01a6237..cc61c1bd1 100644
--- a/app/views/play/level/tome/CastButtonView.coffee
+++ b/app/views/play/level/tome/CastButtonView.coffee
@@ -14,6 +14,7 @@ module.exports = class CastButtonView extends CocoView
   subscriptions:
     'tome:spell-changed': 'onSpellChanged'
     'tome:cast-spells': 'onCastSpells'
+    'tome:manual-cast-denied': 'onManualCastDenied'
     'god:new-world-created': 'onNewWorld'
     'real-time-multiplayer:created-game': 'onJoinedRealTimeMultiplayerGame'
     'real-time-multiplayer:joined-game': 'onJoinedRealTimeMultiplayerGame'
@@ -25,6 +26,11 @@ module.exports = class CastButtonView extends CocoView
     super options
     @spells = options.spells
     @castShortcut = '⇧↵'
+    @updateReplayabilityInterval = setInterval @updateReplayability, 1000
+
+  destroy: ->
+    clearInterval @updateReplayabilityInterval
+    super()
 
   getRenderData: (context={}) ->
     context = super context
@@ -49,6 +55,7 @@ module.exports = class CastButtonView extends CocoView
       @$el.find('.done-button').show()
     if @options.level.get('slug') is 'thornbush-farm'# and not @options.session.get('state')?.complete
       @$el.find('.submit-button').hide()  # Hide submit until first win so that script can explain it.
+    @updateReplayability()
 
   attachTo: (spellView) ->
     @$el.detach().prependTo(spellView.toolbarView.$el).show()
@@ -59,8 +66,11 @@ module.exports = class CastButtonView extends CocoView
   onCastRealTimeButtonClick: (e) ->
     if @inRealTimeMultiplayerSession
       Backbone.Mediator.publish 'real-time-multiplayer:manual-cast', {}
+    else if @options.level.get('replayable') and (timeUntilResubmit = @options.session.timeUntilResubmit()) > 0
+      Backbone.Mediator.publish 'tome:manual-cast-denied', timeUntilResubmit: timeUntilResubmit
     else
       Backbone.Mediator.publish 'tome:manual-cast', {realTime: true}
+    @updateReplayability()
 
   onDoneButtonClick: (e) ->
     Backbone.Mediator.publish 'level:show-victory', showModal: true
@@ -72,14 +82,19 @@ module.exports = class CastButtonView extends CocoView
     return if e.preload
     @casting = true
     if @hasStartedCastingOnce  # Don't play this sound the first time
-      Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'cast', volume: 0.5
+      @playSound 'cast', 0.5
     @hasStartedCastingOnce = true
     @updateCastButton()
 
+  onManualCastDenied: (e) ->
+    wait = moment().add(e.timeUntilResubmit, 'ms').fromNow()
+    #@playSound 'manual-cast-denied', 1.0   # find some sound for this?
+    noty text: "You can try again #{wait}.", layout: 'center', type: 'warning', killer: false, timeout: 6000
+
   onNewWorld: (e) ->
     @casting = false
     if @hasCastOnce  # Don't play this sound the first time
-      Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'cast-end', volume: 0.5
+      @playSound 'cast-end', 0.5
     @hasCastOnce = true
     @updateCastButton()
 
@@ -120,6 +135,17 @@ module.exports = class CastButtonView extends CocoView
       @castButton.text castText
       #@castButton.prop 'disabled', not castable
 
+  updateReplayability: =>
+    return if @destroyed
+    return unless @options.level.get 'replayable'
+    timeUntilResubmit = @options.session.timeUntilResubmit()
+    disabled = timeUntilResubmit > 0
+    submitButton = @$el.find('.submit-button').toggleClass('disabled', disabled)
+    submitAgainLabel = submitButton.find('.submit-again-time').toggleClass('secret', not disabled)
+    if disabled
+      waitTime = moment().add(timeUntilResubmit, 'ms').fromNow()
+      submitAgainLabel.text waitTime
+
   setAutocastDelay: (delay) ->
     #console.log 'Set autocast delay to', delay
     return unless delay
diff --git a/app/views/play/level/tome/SpellView.coffee b/app/views/play/level/tome/SpellView.coffee
index a7ce12e8b..cde4b7ecc 100644
--- a/app/views/play/level/tome/SpellView.coffee
+++ b/app/views/play/level/tome/SpellView.coffee
@@ -122,7 +122,11 @@ module.exports = class SpellView extends CocoView
     addCommand
       name: 'run-code-real-time'
       bindKey: {win: 'Ctrl-Shift-Enter', mac: 'Command-Shift-Enter|Ctrl-Shift-Enter'}
-      exec: -> Backbone.Mediator.publish 'tome:manual-cast', {realTime: true}
+      exec: =>
+        if @options.level.get('replayable') and (timeUntilResubmit = @session.timeUntilResubmit()) > 0
+          Backbone.Mediator.publish 'tome:manual-cast-denied', timeUntilResubmit: timeUntilResubmit
+        else
+          Backbone.Mediator.publish 'tome:manual-cast', {realTime: true}
     addCommand
       name: 'no-op'
       bindKey: {win: 'Ctrl-S', mac: 'Command-S|Ctrl-S'}
@@ -616,7 +620,7 @@ module.exports = class SpellView extends CocoView
     onSignificantChange.push _.debounce @checkSuspectCode, 750 if @options.level.get 'suspectCode'
     @onCodeChangeMetaHandler = =>
       return if @eventsSuppressed
-      Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'code-change', volume: 0.5
+      #@playSound 'code-change', volume: 0.5  # Currently not using this sound.
       if @spellThang
         @spell.hasChangedSignificantly @getSource(), @spellThang.aether.raw, (hasChanged) =>
           if not @spellThang or hasChanged
diff --git a/app/views/play/level/tome/TomeView.coffee b/app/views/play/level/tome/TomeView.coffee
index c27156999..3ba0c88b0 100644
--- a/app/views/play/level/tome/TomeView.coffee
+++ b/app/views/play/level/tome/TomeView.coffee
@@ -163,8 +163,9 @@ module.exports = class TomeView extends CocoView
     if realTime
       sessionState.submissionCount = (sessionState.submissionCount ? 0) + 1
       sessionState.flagHistory = _.filter sessionState.flagHistory ? [], (event) => event.team isnt (@options.session.get('team') ? 'humans')
+      sessionState.lastUnsuccessfulSubmissionTime = new Date() if @options.level.get 'replayable'
       @options.session.set 'state', sessionState
-    Backbone.Mediator.publish 'tome:cast-spells', spells: @spells, preload: preload, realTime: realTime, submissionCount: sessionState.submissionCount ? 0, flagHistory: sessionState.flagHistory ? []
+    Backbone.Mediator.publish 'tome:cast-spells', spells: @spells, preload: preload, realTime: realTime, submissionCount: sessionState.submissionCount ? 0, flagHistory: sessionState.flagHistory ? [], difficulty: sessionState.difficulty ? 0
 
   onToggleSpellList: (e) ->
     @spellList.rerenderEntries()
diff --git a/headless_client/worker_world.coffee b/headless_client/worker_world.coffee
index 9406cec63..b9896f9cf 100644
--- a/headless_client/worker_world.coffee
+++ b/headless_client/worker_world.coffee
@@ -80,6 +80,7 @@ work = () ->
       self.world.levelSessionIDs = args.levelSessionIDs
       self.world.submissionCount = args.submissionCount
       self.world.flagHistory = args.flagHistory
+      self.world.difficulty = args.difficulty
       self.world.loadFromLevel args.level, true if args.level
       self.world.headless = args.headless
       self.goalManager = new GoalManager(self.world)
diff --git a/scripts/analytics/parseMixpanelPayments.py b/scripts/analytics/parseMixpanelPayments.py
index bc0c4bebb..c8f6a3742 100644
--- a/scripts/analytics/parseMixpanelPayments.py
+++ b/scripts/analytics/parseMixpanelPayments.py
@@ -181,8 +181,8 @@ if __name__ == '__main__':
         api_key = sys.argv[1]
         api_secret = sys.argv[2]
         # HoC
-        printPriceConversionRates(api_key, api_secret, '2014-12-08', '2014-12-13')
+        printPriceConversionRates(api_key, api_secret, '2014-12-08', '2014-12-19')
         
         # Use these to feed numbers into Stripe parsing script, since Stripe knows better about conversions than Mixpanel
         print 'Pre-HoC shown', getShownSubModal(api_key, api_secret, '2014-12-06', '2014-12-07')
-        print 'Post-HoC shown', getShownSubModal(api_key, api_secret, '2014-12-14', '2014-12-14')
+        print 'Post-HoC shown', getShownSubModal(api_key, api_secret, '2014-12-20', '2015-01-04')
diff --git a/scripts/analytics/parseStripePayments.py b/scripts/analytics/parseStripePayments.py
index 7cf0cb82e..c548d0ae4 100644
--- a/scripts/analytics/parseStripePayments.py
+++ b/scripts/analytics/parseStripePayments.py
@@ -74,7 +74,7 @@ def getHoCPriceConversionRates(paymentsFile):
             # 'start2': datetime(2014, 12, 11, 0, 34),
             # 'end2': datetime(2014, 12, 12, 3, 21),
             # 'start3': datetime(2014, 12, 13, 17, 30),
-            'Show subscription modal': 45343,
+            'Show subscription modal': 86883,
             'Finished subscription purchase': 0
         },
         '1499': {
@@ -94,7 +94,7 @@ def getHoCPriceConversionRates(paymentsFile):
 
     # Find 'Finished subscription purchase' event from Stripe data
     startDate = datetime(2014, 12, 8)
-    endDate = datetime(2014, 12, 14)
+    endDate = datetime(2014, 12, 20)
     print startDate, 'to', endDate
     with open(paymentsFile) as f:
         first = True
@@ -180,14 +180,14 @@ def getPostHoCPriceConversionRates(paymentsFile):
     # Show count from Mixpanel
     prices = {
         '999': {
-            'Show subscription modal': 2935,
+            'Show subscription modal': 13339,
             'Finished subscription purchase': 0
         }
     }
     
     # Find 'Finished subscription purchase' event from Stripe data
-    startDate = datetime(2014, 12, 14)
-    endDate = datetime(2014, 12, 15)
+    startDate = datetime(2014, 12, 20)
+    endDate = datetime(2015, 1, 4)
     print startDate, 'to', endDate
     with open(paymentsFile) as f:
         first = True
diff --git a/server/analytics/AnalyticsLogEvent.coffee b/server/analytics/AnalyticsLogEvent.coffee
index a81b70c4c..1f82c2780 100644
--- a/server/analytics/AnalyticsLogEvent.coffee
+++ b/server/analytics/AnalyticsLogEvent.coffee
@@ -6,5 +6,6 @@ AnalyticsLogEventSchema = new mongoose.Schema({
     type: Date
     'default': Date.now
 }, {strict: false})
+AnalyticsLogEventSchema.index({event: 1, created: -1})
 
 module.exports = AnalyticsLogEvent = mongoose.model('analytics.log.event', AnalyticsLogEventSchema)
diff --git a/server/analytics/analytics_log_event_handler.coffee b/server/analytics/analytics_log_event_handler.coffee
index 4574d005c..b4aebd189 100644
--- a/server/analytics/analytics_log_event_handler.coffee
+++ b/server/analytics/analytics_log_event_handler.coffee
@@ -21,11 +21,11 @@ class AnalyticsLogEventHandler extends Handler
     instance
 
   getByRelationship: (req, res, args...) ->
-    return @getLevelCompletionsBySlugs(req, res) if args[1] is 'level_completions'
-    return @getCampaignDropOffs(req, res) if args[1] is 'campaign_drop_offs'
+    return @getLevelCompletionsBySlug(req, res) if args[1] is 'level_completions'
+    return @getCampaignCompletionsBySlug(req, res) if args[1] is 'campaign_completions'
     super(arguments...)
 
-  getLevelCompletionsBySlugs: (req, res) ->
+  getLevelCompletionsBySlug: (req, res) ->
     # Returns an array of per-day level starts and finishes
     # Parameters:
     # slug - level slug
@@ -97,8 +97,8 @@ class AnalyticsLogEventHandler extends Handler
         @levelCompletionsCache[cacheKey] = completions[level]
       @sendSuccess res, completions[levelSlug]
 
-  getCampaignDropOffs: (req, res) ->
-    # Returns a dictionary of per-campaign level start and finish drop-offs
+  getCampaignCompletionsBySlug: (req, res) ->
+    # Returns a dictionary of per-campaign level starts, finishes, and drop-offs
     # Drop-off: last started or finished level event
     # Parameters:
     # slugs - array of campaign slugs
@@ -128,7 +128,7 @@ class AnalyticsLogEventHandler extends Handler
     cacheKey += 'e' + endDay if endDay?
     return @sendSuccess res, campaignDropOffs if campaignDropOffs = @campaignDropOffsCache[cacheKey]
 
-    calculateDropOffs = (campaigns) =>
+    getCompletions = (campaigns) =>
       # Calculate campaign drop off rates
       # Input:
       # campaigns - per-campaign dictionary of ordered level slugs
@@ -180,7 +180,7 @@ class AnalyticsLogEventHandler extends Handler
               levelProgression[level].finishDropped++ if i is userProgression[user].length - 1
 
         # Put in campaign order
-        campaignRates = {}
+        completions = {}
         for level of levelProgression
           for campaign of campaigns
             if level in campaigns[campaign]
@@ -188,14 +188,14 @@ class AnalyticsLogEventHandler extends Handler
               startDropped = levelProgression[level].startDropped
               finished = levelProgression[level].finished
               finishDropped = levelProgression[level].finishDropped
-              campaignRates[campaign] ?=
+              completions[campaign] ?=
                 levels: []
                 # overall:
                 #   started: 0,
                 #   startDropped: 0,
                 #   finished: 0,
                 #   finishDropped: 0
-              campaignRates[campaign].levels.push
+              completions[campaign].levels.push
                 level: level
                 started: started
                 startDropped: startDropped
@@ -204,19 +204,19 @@ class AnalyticsLogEventHandler extends Handler
               break
 
         # Sort level data by campaign order
-        for campaign of campaignRates
-          campaignRates[campaign].levels.sort (a, b) ->
+        for campaign of completions
+          completions[campaign].levels.sort (a, b) ->
             if campaigns[campaign].indexOf(a.level) < campaigns[campaign].indexOf(b.level) then return -1 else 1
 
         # Return all campaign data for simplicity
         # Cache other individual campaigns too, since we have them
-        @campaignDropOffsCache[cacheKey] = campaignRates
-        for campaign of campaignRates
+        @campaignDropOffsCache[cacheKey] = completions
+        for campaign of completions
           cacheKey = campaign
           cacheKey += 's' + startDay if startDay?
           cacheKey += 'e' + endDay if endDay?
-          @campaignDropOffsCache[cacheKey] = campaignRates
-        @sendSuccess res, campaignRates
+          @campaignDropOffsCache[cacheKey] = completions
+        @sendSuccess res, completions
 
     getLevelData = (campaigns, campaignLevelIDs) =>
       # Get level data and replace levelIDs with level slugs in campaigns
@@ -239,10 +239,8 @@ class AnalyticsLogEventHandler extends Handler
         for campaign of campaigns
           mapFn = (item) -> levelSlugMap[item]
           campaigns[campaign] = _.map campaigns[campaign], mapFn, @
-          # Forest campaign levels are reversed for some reason
-          campaigns[campaign].reverse() if campaign is 'forest'
 
-        calculateDropOffs campaigns
+        getCompletions campaigns
 
     getCampaignData = () =>
       # Get campaign data 
diff --git a/server/levels/level_handler.coffee b/server/levels/level_handler.coffee
index 03879c0c7..6f3626992 100644
--- a/server/levels/level_handler.coffee
+++ b/server/levels/level_handler.coffee
@@ -55,6 +55,7 @@ LevelHandler = class LevelHandler extends Handler
     'tasks'
     'helpVideos'
     'campaign'
+    'replayable'
   ]
 
   postEditableProperties: ['name']
@@ -383,7 +384,7 @@ LevelHandler = class LevelHandler extends Handler
       # Build list of level average playtimes
       playtimes = []
       for item in data
-        playtimes.push 
+        playtimes.push
           level: item._id.level
           created: item._id.created
           average: item.average
diff --git a/server/levels/sessions/level_session_handler.coffee b/server/levels/sessions/level_session_handler.coffee
index 62637c177..386ead7e3 100644
--- a/server/levels/sessions/level_session_handler.coffee
+++ b/server/levels/sessions/level_session_handler.coffee
@@ -9,6 +9,7 @@ class LevelSessionHandler extends Handler
 
   getByRelationship: (req, res, args...) ->
     return @getActiveSessions req, res if args.length is 2 and args[1] is 'active'
+    return @getRecentSessions req, res if args.length is 2 and args[1] is 'recent'
     return @getCodeLanguageCounts req, res if args[1] is 'code_language_counts'
     super(arguments...)
 
@@ -29,6 +30,19 @@ class LevelSessionHandler extends Handler
       documents = (@formatEntity(req, doc) for doc in documents)
       @sendSuccess(res, documents)
 
+  getRecentSessions: (req, res) ->
+    return @sendForbiddenError(res) unless req.user?.isAdmin()
+
+    levelSlug = req.query.slug or req.body.slug
+    limit = req.query.limit or req.body.limit or 7
+
+    return @sendSuccess res, [] unless levelSlug?
+
+    query = @modelClass.find({"levelID": levelSlug}).sort({changed: -1}).limit(limit)
+    query.exec (err, documents) =>
+      return @sendDatabaseError(res, err) if err
+      @sendSuccess res, documents
+
   hasAccessToDocument: (req, document, method=null) ->
     return true if req.method is 'GET' and document.get('submitted')
     return true if ('employer' in (req.user?.get('permissions') ? [])) and (method ? req.method).toLowerCase() is 'get'
diff --git a/server/plugins/achievements.coffee b/server/plugins/achievements.coffee
index acb080812..639e79a14 100644
--- a/server/plugins/achievements.coffee
+++ b/server/plugins/achievements.coffee
@@ -16,7 +16,8 @@ AchievablePlugin = (schema, options) ->
   return
   # Keep track the document before it's saved
   schema.post 'init', (doc) ->
-    before[doc.id] = doc.toObject()
+    #doc.beforeDoc = doc.toObject()  # TODO: switch to this
+    before[doc.id] = doc.toObject()  # TODO: switch from this, run the testzzz
     # TODO check out how many objects go unreleased
 
   # Check if an achievement has been earned
@@ -43,6 +44,6 @@ AchievablePlugin = (schema, options) ->
           return unless newlyAchieved and (not alreadyAchieved or isRepeatable)
           EarnedAchievement.createForAchievement(achievement, doc, originalDocObj)
 
-    delete before[doc.id] if doc.id of before
+    delete before[doc.id] if doc.id of before  # TODO: don't do it!
 
 module.exports = AchievablePlugin
diff --git a/server/user_code_problems/user_code_problem_handler.coffee b/server/user_code_problems/user_code_problem_handler.coffee
index a4424b6f9..268458c92 100644
--- a/server/user_code_problems/user_code_problem_handler.coffee
+++ b/server/user_code_problems/user_code_problem_handler.coffee
@@ -23,4 +23,62 @@ class UserCodeProblemHandler extends Handler
     ucp.set('creator', req.user._id)
     ucp
 
+  getByRelationship: (req, res, args...) ->
+    return @getCommonLevelProblemsBySlug(req, res) if args[1] is 'common_problems'
+    super(arguments...)
+
+  getCommonLevelProblemsBySlug: (req, res) ->
+    # Returns an ordered array of common user code problems with: language, message, hint, count
+    # Parameters:
+    # slug - level slug
+    # startDay - Inclusive, optional, e.g. '2014-12-14'
+    # endDay - Exclusive, optional, e.g. '2014-12-16'
+
+    levelSlug = req.query.slug or req.body.slug
+    startDay = req.query.startDay or req.body.startDay
+    endDay = req.query.endDay or req.body.endDay
+
+    return @sendSuccess res, [] unless levelSlug?
+
+    # Cache results for 1 day
+    @commonLevelProblemsCache ?= {}
+    @commonLevelProblemsCachedSince ?= new Date()
+    if (new Date()) - @commonLevelProblemsCachedSince > 86400 * 1000  # Dumb cache expiration
+      @commonLevelProblemsCache = {}
+      @commonLevelProblemsCachedSince = new Date()
+    cacheKey = levelSlug
+    cacheKey += 's' + startDay if startDay?
+    cacheKey += 'e' + endDay if endDay?
+    return @sendSuccess res, commonProblems if commonProblems = @commonLevelProblemsCache[cacheKey]
+
+    # Build query
+    match = if startDay? or endDay? then {$match: {$and: []}} else {$match: {}}
+    match["$match"]["$and"].push created: {$gte: new Date(startDay + "T00:00:00.000Z")} if startDay?
+    match["$match"]["$and"].push created: {$lt: new Date(endDay + "T00:00:00.000Z")} if endDay?
+    group = {"$group": {"_id": {"errMessage": "$errMessageNoLineInfo", "errHint": "$errHint", "language": "$language", "levelID": "$levelID"}, "count": {"$sum": 1}}}
+    sort = { $sort : { "_id.levelID": 1, count : -1, "_id.language": 1 } }
+    query = UserCodeProblem.aggregate match, group, sort
+
+    query.exec (err, data) =>
+      if err? then return @sendDatabaseError res, err
+
+      # Build per-level common problem lists
+      commonProblems = {}
+      for item in data
+        levelID = item._id.levelID
+        commonProblems[levelID] ?= []
+        commonProblems[levelID].push
+          language: item._id.language
+          message: item._id.errMessage
+          hint: item._id.errHint
+          count: item.count
+
+      # Cache all the levels
+      for levelID of commonProblems
+        cacheKey = levelID
+        cacheKey += 's' + startDay if startDay?
+        cacheKey += 'e' + endDay if endDay?
+        @commonLevelProblemsCache[cacheKey] = commonProblems[levelID]
+      @sendSuccess res, commonProblems[levelSlug]
+
 module.exports = new UserCodeProblemHandler()
diff --git a/server_setup.coffee b/server_setup.coffee
index 7b99ff29d..df81d50a0 100644
--- a/server_setup.coffee
+++ b/server_setup.coffee
@@ -127,26 +127,15 @@ setupJavascript404s = (app) ->
 
 setupFallbackRouteToIndex = (app) ->
   app.all '*', (req, res) ->
-    if req.user
-      sendMain(req, res)
-      # Disabling for HoC
-#      req.user.set('lastIP', req.connection.remoteAddress)
-#      req.user.save()
-    else
-      user = auth.makeNewUser(req)
-      makeNext = (req, res) -> -> sendMain(req, res)
-      next = makeNext(req, res)
-      auth.loginUser(req, res, user, false, next)
-
-sendMain = (req, res) ->
-  fs.readFile path.join(__dirname, 'public', 'main.html'), 'utf8', (err, data) ->
-    log.error "Error modifying main.html: #{err}" if err
-    # insert the user object directly into the html so the application can have it immediately. Sanitize </script>
-    data = data.replace('"userObjectTag"', JSON.stringify(UserHandler.formatEntity(req, req.user)).replace(/\//g, '\\/'))
-    res.header 'Cache-Control', 'no-cache, no-store, must-revalidate'
-    res.header 'Pragma', 'no-cache'
-    res.header 'Expires', 0
-    res.send 200, data
+    fs.readFile path.join(__dirname, 'public', 'main.html'), 'utf8', (err, data) ->
+      log.error "Error modifying main.html: #{err}" if err
+      # insert the user object directly into the html so the application can have it immediately. Sanitize </script>
+      user = if req.user then JSON.stringify(UserHandler.formatEntity(req, req.user)).replace(/\//g, '\\/') else '{}'
+      data = data.replace('"userObjectTag"', user)
+      res.header 'Cache-Control', 'no-cache, no-store, must-revalidate'
+      res.header 'Pragma', 'no-cache'
+      res.header 'Expires', 0
+      res.send 200, data
 
 setupFacebookCrossDomainCommunicationRoute = (app) ->
   app.get '/channel.html', (req, res) ->