diff --git a/js/spniEpilogue.js b/js/spniEpilogue.js
index d26570a3552694f54721cf6ff2d61b6bc4692756..0d0a2fdc0bd671c3aaa7b08c24876417a483e8c5 100644
--- a/js/spniEpilogue.js
+++ b/js/spniEpilogue.js
@@ -28,27 +28,27 @@ var epilogueSuffix = 0;
 // Attach some event listeners
 var previousButton = document.getElementById('epilogue-previous');
 var nextButton = document.getElementById('epilogue-next');
-previousButton.addEventListener('click', function(e) {
+previousButton.addEventListener('click', function (e) {
   e.preventDefault();
   e.stopPropagation();
   moveEpilogueBack();
 });
-nextButton.addEventListener('click', function(e) {
+nextButton.addEventListener('click', function (e) {
   e.preventDefault();
   e.stopPropagation();
   moveEpilogueForward();
 });
-document.getElementById('epilogue-restart').addEventListener('click', function(e) {
+document.getElementById('epilogue-restart').addEventListener('click', function (e) {
   e.preventDefault();
   e.stopPropagation();
   showRestartModal();
 });
-document.getElementById('epilogue-buttons').addEventListener('click', function() {
+document.getElementById('epilogue-buttons').addEventListener('click', function () {
   if (!previousButton.disabled) {
     moveEpilogueBack();
   }
 });
-epilogueContainer.addEventListener('click', function() {
+epilogueContainer.addEventListener('click', function () {
   if (!nextButton.disabled) {
     moveEpilogueForward();
   }
@@ -164,21 +164,18 @@ Animation.prototype.halt = function () {
 /************************************************************
  * Creates a closure in order to maintain a function's "this"
  ************************************************************/
-function createClosure(instance, func)
-{
-	var $this = instance;
-	return function ()
-	{
-		func.apply($this, arguments);
-	};
+function createClosure(instance, func) {
+  var $this = instance;
+  return function () {
+    func.apply($this, arguments);
+  };
 }
 
 /************************************************************
  * Linear interpolation
  ************************************************************/
-function lerp(a, b, t)
-{
-	return (b - a) * t + a;
+function lerp(a, b, t) {
+  return (b - a) * t + a;
 }
 
 /************************************************************
@@ -194,193 +191,187 @@ var clampingFunctions = {
  * Interpolation functions for animation movement interpolation
  ************************************************************/
 var interpolationModes = {
-	"linear": function linear(prop, start, end, t, frames, index)
-	{
-		return lerp(start, end, t);
-	},
-	"spline": function catmullrom(prop, start, end, t, frames, index)
-	{
-		var p0 = index > 0 ? frames[index - 1][prop] : start;
-		var p1 = start;
-		var p2 = end;
-		var p3 = index < frames.length - 2 ? frames[index + 1][prop] : end;
-
-		if (typeof p0 === "undefined" || isNaN(p0))
-		{
-			p0 = start;
-		}
-		if (typeof p3 === "undefined" || isNaN(p3))
-		{
-			p3 = end;
-		}
-
-		var a = 2 * p1;
-		var b = p2 - p0;
-		var c = 2 * p0 - 5 * p1 + 4 * p2 - p3;
-		var d = -p0 + 3 * p1 - 3 * p2 + p3;
-
-		var p = 0.5 * (a + (b * t) + (c * t * t) + (d * t * t * t));
-		return p;
-	},
+  "none": function noInterpolation(prop, start, end, t, frames, index) {
+    return t >= 1 ? end : start;
+
+  },
+  "linear": function linear(prop, start, end, t, frames, index) {
+    return lerp(start, end, t);
+  },
+  "spline": function catmullrom(prop, start, end, t, frames, index) {
+    var p0 = index > 0 ? frames[index - 1][prop] : start;
+    var p1 = start;
+    var p2 = end;
+    var p3 = index < frames.length - 2 ? frames[index + 1][prop] : end;
+
+    if (typeof p0 === "undefined" || isNaN(p0)) {
+      p0 = start;
+    }
+    if (typeof p3 === "undefined" || isNaN(p3)) {
+      p3 = end;
+    }
+
+    var a = 2 * p1;
+    var b = p2 - p0;
+    var c = 2 * p0 - 5 * p1 + 4 * p2 - p3;
+    var d = -p0 + 3 * p1 - 3 * p2 + p3;
+
+    var p = 0.5 * (a + (b * t) + (c * t * t) + (d * t * t * t));
+    return p;
+  },
 };
 
 /************************************************************
  * Converts a px or % value to the equivalent scene value
  ************************************************************/
-function toSceneX(x, scene)
-{
-	if (typeof x === "undefined") { return; }
-	if ($.isNumeric(x)) { return parseInt(x, 10); }
-	if (x.endsWith("%"))
-	{
-		return parseInt(x, 10) / 100 * scene.width;
-	}
-	else
-	{
-		return parseInt(x, 10);
-	}
+function toSceneX(x, scene) {
+  if (typeof x === "undefined") { return; }
+  if ($.isNumeric(x)) { return parseInt(x, 10); }
+  if (x.endsWith("%")) {
+    return parseInt(x, 10) / 100 * scene.width;
+  }
+  else {
+    return parseInt(x, 10);
+  }
 }
 
 /************************************************************
  * Converts a px or % value to the equivalent scene value
  ************************************************************/
-function toSceneY(y, scene)
-{
-	if (typeof y === "undefined") { return; }
-	if ($.isNumeric(y)) { return parseInt(y, 10); }
-	if (y.endsWith("%"))
-	{
-		return parseInt(y, 10) / 100 * scene.height;
-	}
-	else
-	{
-		return parseInt(y, 10);
-	}
+function toSceneY(y, scene) {
+  if (typeof y === "undefined") { return; }
+  if ($.isNumeric(y)) { return parseInt(y, 10); }
+  if (y.endsWith("%")) {
+    return parseInt(y, 10) / 100 * scene.height;
+  }
+  else {
+    return parseInt(y, 10);
+  }
 }
 
 /************************************************************
  * Return the numerical part of a string s. E.g. "20%" -> 20
  ************************************************************/
-function getNumericalPart(s){
-	return parseFloat(s); //apparently I don't actually need to remove the % (or anything else) from the string before I do the conversion
+function getNumericalPart(s) {
+  return parseFloat(s); //apparently I don't actually need to remove the % (or anything else) from the string before I do the conversion
 }
 
 /************************************************************
  * Return the approriate left: setting so that a text box of the specified width is centred
  * Assumes a % width
  ************************************************************/
-function getCenteredPosition(width){
-	var w = getNumericalPart(width); //numerical value of the width
-	var left = 50 - (w/2); //centre of the text box is at the 50% position
-	return left + "%";
+function getCenteredPosition(width) {
+  var w = getNumericalPart(width); //numerical value of the width
+  var left = 50 - (w / 2); //centre of the text box is at the 50% position
+  return left + "%";
 }
 
 /************************************************************
  * Load the Epilogue data for a character
  ************************************************************/
 function loadEpilogueData(player) {
-    if (!player || !player.xml) { //return an empty list if a character doesn't have an XML variable. (Most likely because they're the player.)
-        return [];
-    }
+  if (!player || !player.xml) { //return an empty list if a character doesn't have an XML variable. (Most likely because they're the player.)
+    return [];
+  }
 
-	var playerGender = players[HUMAN_PLAYER].gender;
+  var playerGender = players[HUMAN_PLAYER].gender;
 
-	//get the XML tree that relates to the epilogue, for the specific player gender
-	//var epXML = $($.parseXML(xml)).find('epilogue[gender="'+playerGender+'"]'); //use parseXML() so that <image> tags come through properly //IE doesn't like this
+  //get the XML tree that relates to the epilogue, for the specific player gender
+  //var epXML = $($.parseXML(xml)).find('epilogue[gender="'+playerGender+'"]'); //use parseXML() so that <image> tags come through properly //IE doesn't like this
 
   var epilogues = player.xml.find('epilogue').filter(function (index) {
-      /* Returning true from this function adds the current epilogue to the list of selectable epilogues.
-       * Conversely, returning false from this function will make the current epilogue not selectable.
-       */
-
-      /* 'gender' attribute: the epilogue will only be selectable if the player character has the given gender, or if the epilogue is marked for 'any' gender. */
-      var epilogue_gender = $(this).attr('gender');
-      if (epilogue_gender && epilogue_gender !== playerGender && epilogue_gender !== 'any') {
-          // if the gender doesn't match, don't make this epilogue selectable
-          return false;
-      }
-
-      var alsoPlaying = $(this).attr('alsoPlaying');
-      if (alsoPlaying !== undefined && !(players.some(function(p) { return p.id == alsoPlaying; }))) {
-          return false;
-      }
-
-      var playerStartingLayers = parseInterval($(this).attr('playerStartingLayers'));
-      if (playerStartingLayers !== undefined && !inInterval(players[HUMAN_PLAYER].startingLayers, playerStartingLayers)) {
-          return false;
-      }
-
-      /* 'markers' attribute: the epilogue will only be selectable if the character has ALL markers listed within the attribute set. */
-      var all_marker_attr = $(this).attr('markers');
-      if (all_marker_attr
-          && !all_marker_attr.trim().split(/\s+/).every(function(marker) {
-              return checkMarker(marker, player);
-          })) {
-          // not every marker set
-          return false;
-      }
-
-      /* 'not-markers' attribute: the epilogue will only be selectable if the character has NO markers listed within the attribute set. */
-      var no_marker_attr = $(this).attr('not-markers');
-      if (no_marker_attr
-          && no_marker_attr.trim().split(/\s+/).some(function(marker) {
-              return checkMarker(marker, player);
-          })) {
-          // some disallowed marker set
-          return false;
-      }
-
-      /* 'any-markers' attribute: the epilogue will only be selectable if the character has at least ONE of the markers listed within the attribute set. */
-      var any_marker_attr = $(this).attr('any-markers');
-      if (any_marker_attr
-          && !any_marker_attr.trim().split(/\s+/).some(function(marker) {
-              return checkMarker(marker, player);
-          })) {
-          // none of the markers set
-          return false;
-      }
-
-      /* 'alsoplaying-markers' attribute: this epilogue will only be selectable if ALL markers within the attribute are set for any OTHER characters in the game. */
-      var alsoplaying_marker_attr = $(this).attr('alsoplaying-markers');
-      if (alsoplaying_marker_attr
-          && !alsoplaying_marker_attr.trim().split(/\s+/).every(function(marker) {
-              return players.some(function(p) {
-                  return p !== player && checkMarker(marker, p);
-              });
-          })) {
-          // not every marker set by some other character
-          return false;
-      }
+    /* Returning true from this function adds the current epilogue to the list of selectable epilogues.
+     * Conversely, returning false from this function will make the current epilogue not selectable.
+     */
+
+    /* 'gender' attribute: the epilogue will only be selectable if the player character has the given gender, or if the epilogue is marked for 'any' gender. */
+    var epilogue_gender = $(this).attr('gender');
+    if (epilogue_gender && epilogue_gender !== playerGender && epilogue_gender !== 'any') {
+      // if the gender doesn't match, don't make this epilogue selectable
+      return false;
+    }
+
+    var alsoPlaying = $(this).attr('alsoPlaying');
+    if (alsoPlaying !== undefined && !(players.some(function (p) { return p.id == alsoPlaying; }))) {
+      return false;
+    }
+
+    var playerStartingLayers = parseInterval($(this).attr('playerStartingLayers'));
+    if (playerStartingLayers !== undefined && !inInterval(players[HUMAN_PLAYER].startingLayers, playerStartingLayers)) {
+      return false;
+    }
+
+    /* 'markers' attribute: the epilogue will only be selectable if the character has ALL markers listed within the attribute set. */
+    var all_marker_attr = $(this).attr('markers');
+    if (all_marker_attr
+      && !all_marker_attr.trim().split(/\s+/).every(function (marker) {
+        return checkMarker(marker, player);
+      })) {
+      // not every marker set
+      return false;
+    }
+
+    /* 'not-markers' attribute: the epilogue will only be selectable if the character has NO markers listed within the attribute set. */
+    var no_marker_attr = $(this).attr('not-markers');
+    if (no_marker_attr
+      && no_marker_attr.trim().split(/\s+/).some(function (marker) {
+        return checkMarker(marker, player);
+      })) {
+      // some disallowed marker set
+      return false;
+    }
+
+    /* 'any-markers' attribute: the epilogue will only be selectable if the character has at least ONE of the markers listed within the attribute set. */
+    var any_marker_attr = $(this).attr('any-markers');
+    if (any_marker_attr
+      && !any_marker_attr.trim().split(/\s+/).some(function (marker) {
+        return checkMarker(marker, player);
+      })) {
+      // none of the markers set
+      return false;
+    }
+
+    /* 'alsoplaying-markers' attribute: this epilogue will only be selectable if ALL markers within the attribute are set for any OTHER characters in the game. */
+    var alsoplaying_marker_attr = $(this).attr('alsoplaying-markers');
+    if (alsoplaying_marker_attr
+      && !alsoplaying_marker_attr.trim().split(/\s+/).every(function (marker) {
+        return players.some(function (p) {
+          return p !== player && checkMarker(marker, p);
+        });
+      })) {
+      // not every marker set by some other character
+      return false;
+    }
 
-      /* 'alsoplaying-not-markers' attribute: this epilogue will only be selectable if NO markers within the attribute are set for other characters in the game. */
-      var alsoplaying_not_marker_attr = $(this).attr('alsoplaying-not-markers');
-      if (alsoplaying_not_marker_attr
-          && alsoplaying_not_marker_attr.trim().split(/\s+/).some(function(marker) {
-              return players.some(function(p) {
-                  return p !== player && checkMarker(marker, p);
-              });
-          })) {
-          // some disallowed marker set by some other character
-          return false;
-      }
+    /* 'alsoplaying-not-markers' attribute: this epilogue will only be selectable if NO markers within the attribute are set for other characters in the game. */
+    var alsoplaying_not_marker_attr = $(this).attr('alsoplaying-not-markers');
+    if (alsoplaying_not_marker_attr
+      && alsoplaying_not_marker_attr.trim().split(/\s+/).some(function (marker) {
+        return players.some(function (p) {
+          return p !== player && checkMarker(marker, p);
+        });
+      })) {
+      // some disallowed marker set by some other character
+      return false;
+    }
 
-      /* 'alsoplaying-any-markers' attribute: this epilogue will only be selectable if at least one marker within the attribute are set for any OTHER character in the game. */
-      var alsoplaying_any_marker_attr = $(this).attr('alsoplaying-any-markers');
-      if (alsoplaying_any_marker_attr
-          && !alsoplaying_any_marker_attr.trim().split(/\s+/).some(function(marker) {
-              return players.some(function(p) {
-                  return p !== player && checkMarker(marker, p);
-              });
-          })) {
-          // none of the markers set by any other player
-          return false;
-      }
+    /* 'alsoplaying-any-markers' attribute: this epilogue will only be selectable if at least one marker within the attribute are set for any OTHER character in the game. */
+    var alsoplaying_any_marker_attr = $(this).attr('alsoplaying-any-markers');
+    if (alsoplaying_any_marker_attr
+      && !alsoplaying_any_marker_attr.trim().split(/\s+/).some(function (marker) {
+        return players.some(function (p) {
+          return p !== player && checkMarker(marker, p);
+        });
+      })) {
+      // none of the markers set by any other player
+      return false;
+    }
 
-      // if we made it this far the epilogue must be selectable
-      return true;
+    // if we made it this far the epilogue must be selectable
+    return true;
   }).map(function (i, e) { return parseEpilogue(player, e); }).get();
 
-	return epilogues;
+  return epilogues;
 }
 
 function parseEpilogue(player, rawEpilogue, galleryEnding) {
@@ -466,7 +457,7 @@ function parseEpilogue(player, rawEpilogue, galleryEnding) {
             directive.keyframes.push(keyframe);
           });
           if (directive.keyframes.length === 0) {
-            //if no explicity keyframes were provided, use the directive itself as a keyframe
+            //if no explicit keyframes were provided, use the directive itself as a keyframe
             directive.start = 0;
             directive.end = directive.time;
             directive.keyframes.push(directive);
@@ -622,13 +613,13 @@ function parseSceneContent(player, scene, $scene) {
     addedPause = true;
   });
   if (!addedPause) {
-      scene.directives.push({ type: "pause" });
+    scene.directives.push({ type: "pause" });
   }
 }
 
- /************************************************************
- * Read attributes from a source XML object and put them into properties of a JS object
- ************************************************************/
+/************************************************************
+* Read attributes from a source XML object and put them into properties of a JS object
+************************************************************/
 function readProperties(sourceObj, scene) {
   var targetObj = {};
   var $obj = $(sourceObj);
@@ -705,36 +696,36 @@ function readProperties(sourceObj, scene) {
  * Add the epilogue to the Epilogue modal
  ************************************************************/
 
-function addEpilogueEntry(epilogue){
-	var num = epilogues.length; //index number of the new epilogue
-	epilogues.push(epilogue);
-	var player = epilogue.player
+function addEpilogueEntry(epilogue) {
+  var num = epilogues.length; //index number of the new epilogue
+  epilogues.push(epilogue);
+  var player = epilogue.player
 
-	var nameStr = player.first+" "+player.last;
-	if (player.first.length <= 0 || player.last.length <= 0){
-		nameStr = player.first+player.last; //only use a space if they have both first and last names
-	}
+  var nameStr = player.first + " " + player.last;
+  if (player.first.length <= 0 || player.last.length <= 0) {
+    nameStr = player.first + player.last; //only use a space if they have both first and last names
+  }
 
-	var epilogueTitle = nameStr+": "+epilogue.title;
-	var idName = 'epilogue-option-'+num;
-	var clickAction = "selectEpilogue("+num+")";
-	var unlocked = save.hasEnding(player.id, epilogue.title) ? " unlocked" : "";
+  var epilogueTitle = nameStr + ": " + epilogue.title;
+  var idName = 'epilogue-option-' + num;
+  var clickAction = "selectEpilogue(" + num + ")";
+  var unlocked = save.hasEnding(player.id, epilogue.title) ? " unlocked" : "";
 
-	var htmlStr = '<li id="'+idName+'" class="epilogue-entry'+unlocked+'"><button onclick="'+clickAction+'">'+epilogueTitle+'</button></li>';
+  var htmlStr = '<li id="' + idName + '" class="epilogue-entry' + unlocked + '"><button onclick="' + clickAction + '">' + epilogueTitle + '</button></li>';
 
-	$epilogueList.append(htmlStr);
-	epilogueSelections.push($('#'+idName));
+  $epilogueList.append(htmlStr);
+  epilogueSelections.push($('#' + idName));
 }
 
 /************************************************************
  * Clear the Epilogue modal
  ************************************************************/
 
-function clearEpilogueList(){
-	$epilogueHeader.html('');
-	$epilogueList.html('');
-	epilogues = [];
-	epilogueSelections = [];
+function clearEpilogueList() {
+  $epilogueHeader.html('');
+  $epilogueList.html('');
+  epilogues = [];
+  epilogueSelections = [];
 }
 
 /************************************************************
@@ -751,113 +742,113 @@ function clearEpilogue() {
  * The user has clicked on a button to choose a particular Epilogue
  ************************************************************/
 
-function selectEpilogue(epNumber){
-	chosenEpilogue = epilogues[epNumber]; //select the chosen epilogues
+function selectEpilogue(epNumber) {
+  chosenEpilogue = epilogues[epNumber]; //select the chosen epilogues
 
-	for (var i = 0; i < epilogues.length; i++){
-		epilogueSelections[i].removeClass("active"); //make sure no other epilogue is selected
-	}
-	epilogueSelections[epNumber].addClass("active"); //mark the selected epilogue as selected
-	$epilogueAcceptButton.prop("disabled", false); //allow the player to accept the epilogue
+  for (var i = 0; i < epilogues.length; i++) {
+    epilogueSelections[i].removeClass("active"); //make sure no other epilogue is selected
+  }
+  epilogueSelections[epNumber].addClass("active"); //mark the selected epilogue as selected
+  $epilogueAcceptButton.prop("disabled", false); //allow the player to accept the epilogue
 }
 
 /************************************************************
  * Show the modal for the player to choose an Epilogue, or restart the game.
  ************************************************************/
-function doEpilogueModal(){
-
-	clearEpilogueList(); //remove any already loaded epilogues
-	chosenEpilogue = null; //reset any currently-chosen epilogue
-	$epilogueAcceptButton.prop("disabled", true); //don't let the player accept an epilogue until they've chosen one
-
-	//whether or not the human player won
-	var playerWon = !players[HUMAN_PLAYER].out;
-
-	if (EPILOGUES_ENABLED && playerWon) { //all the epilogues are for when the player wins, so don't allow them to choose one if they lost
-		//load the epilogue data for each player
-		players.forEach(function(p) {
-			loadEpilogueData(p).forEach(addEpilogueEntry);
-		});
-	}
-
-	//are there any epilogues available for the player to see?
-	var haveEpilogues = (epilogues.length >= 1); //whether or not there are any epilogues available
-	$epilogueAcceptButton.css("visibility", haveEpilogues ? "visible" : "hidden");
-
-    if (EPILOGUES_ENABLED) {
-        //decide which header string to show the player. This describes the situation.
-    	var headerStr = '';
-    	if (playerWon){
-    		headerStr = winStr; //player won, and there are epilogues available
-    		if (!haveEpilogues){
-    			headerStr = winStrNone; //player won, but none of the NPCs have epilogues
-    		}
-    	} else {
-    		headerStr = lossStr; //player lost
-    	}
+function doEpilogueModal() {
+
+  clearEpilogueList(); //remove any already loaded epilogues
+  chosenEpilogue = null; //reset any currently-chosen epilogue
+  $epilogueAcceptButton.prop("disabled", true); //don't let the player accept an epilogue until they've chosen one
+
+  //whether or not the human player won
+  var playerWon = !players[HUMAN_PLAYER].out;
+
+  if (EPILOGUES_ENABLED && playerWon) { //all the epilogues are for when the player wins, so don't allow them to choose one if they lost
+    //load the epilogue data for each player
+    players.forEach(function (p) {
+      loadEpilogueData(p).forEach(addEpilogueEntry);
+    });
+  }
+
+  //are there any epilogues available for the player to see?
+  var haveEpilogues = (epilogues.length >= 1); //whether or not there are any epilogues available
+  $epilogueAcceptButton.css("visibility", haveEpilogues ? "visible" : "hidden");
+
+  if (EPILOGUES_ENABLED) {
+    //decide which header string to show the player. This describes the situation.
+    var headerStr = '';
+    if (playerWon) {
+      headerStr = winStr; //player won, and there are epilogues available
+      if (!haveEpilogues) {
+        headerStr = winStrNone; //player won, but none of the NPCs have epilogues
+      }
     } else {
-        if (playerWon) {
-            headerStr = winEpiloguesDisabledStr;
-        } else {
-            headerStr = lossEpiloguesDisabledStr;
-        }
+      headerStr = lossStr; //player lost
     }
+  } else {
+    if (playerWon) {
+      headerStr = winEpiloguesDisabledStr;
+    } else {
+      headerStr = lossEpiloguesDisabledStr;
+    }
+  }
 
-	$epilogueHeader.html(headerStr); //set the header string
-	$epilogueSelectionModal.modal("show");//show the epilogue selection modal
+  $epilogueHeader.html(headerStr); //set the header string
+  $epilogueSelectionModal.modal("show");//show the epilogue selection modal
 }
 
 /************************************************************
  * Start the Epilogue
  ************************************************************/
-function doEpilogue(){
-	save.addEnding(chosenEpilogue.player.id, chosenEpilogue.title);
-
-	if (USAGE_TRACKING) {
-		var usage_tracking_report = {
-			'date': (new Date()).toISOString(),
-            'commit': VERSION_COMMIT,
-			'type': 'epilogue',
-			'session': sessionID,
-			'game': gameID,
-			'userAgent': navigator.userAgent,
-			'origin': getReportedOrigin(),
-			'table': {},
-			'chosen': {
-				'id': chosenEpilogue.player.id,
-				'title': chosenEpilogue.title
-			}
-		};
-
-		for (let i=1;i<5;i++) {
-			if (players[i]) {
-				usage_tracking_report.table[i] = players[i].id;
-			}
-		}
-
-		$.ajax({
-			url: USAGE_TRACKING_ENDPOINT,
-			method: 'POST',
-			data: JSON.stringify(usage_tracking_report),
-			contentType: 'application/json',
-			error: function (jqXHR, status, err) {
-				console.error("Could not send usage tracking report - error "+status+": "+err);
-			},
-		});
-	}
+function doEpilogue() {
+  save.addEnding(chosenEpilogue.player.id, chosenEpilogue.title);
+
+  if (USAGE_TRACKING) {
+    var usage_tracking_report = {
+      'date': (new Date()).toISOString(),
+      'commit': VERSION_COMMIT,
+      'type': 'epilogue',
+      'session': sessionID,
+      'game': gameID,
+      'userAgent': navigator.userAgent,
+      'origin': getReportedOrigin(),
+      'table': {},
+      'chosen': {
+        'id': chosenEpilogue.player.id,
+        'title': chosenEpilogue.title
+      }
+    };
+
+    for (let i = 1; i < 5; i++) {
+      if (players[i]) {
+        usage_tracking_report.table[i] = players[i].id;
+      }
+    }
+
+    $.ajax({
+      url: USAGE_TRACKING_ENDPOINT,
+      method: 'POST',
+      data: JSON.stringify(usage_tracking_report),
+      contentType: 'application/json',
+      error: function (jqXHR, status, err) {
+        console.error("Could not send usage tracking report - error " + status + ": " + err);
+      },
+    });
+  }
 
   epilogueContainer.dataset.background = -1;
   epilogueContainer.dataset.scene = -1;
 
   loadEpilogue(chosenEpilogue);
 
-	screenTransition($titleScreen, $epilogueScreen); //currently transitioning from title screen, because this is for testing
-	$epilogueSelectionModal.modal("hide");
+  screenTransition($titleScreen, $epilogueScreen); //currently transitioning from title screen, because this is for testing
+  $epilogueSelectionModal.modal("hide");
 }
 
- /************************************************************
- * Starts up an epilogue, pre-fetching all its images before displaying anything in order to handle certain computations that rely on the image sizes
- ************************************************************/
+/************************************************************
+* Starts up an epilogue, pre-fetching all its images before displaying anything in order to handle certain computations that rely on the image sizes
+************************************************************/
 function loadEpilogue(epilogue) {
   $("#epilogue-spinner").show();
   epiloguePlayer = new EpiloguePlayer(epilogue);
@@ -896,9 +887,9 @@ function updateEpilogueButtons() {
   $epilogueRestartButton.prop("disabled", epiloguePlayer.hasMoreDirectives());
 }
 
- /************************************************************
- * Class for playing through an epilogue
- ************************************************************/
+/************************************************************
+* Class for playing through an epilogue
+************************************************************/
 function EpiloguePlayer(epilogue) {
   $(window).resize(createClosure(this, this.resizeViewport));
   this.epilogue = epilogue;
@@ -928,6 +919,13 @@ EpiloguePlayer.prototype.load = function () {
         directive.src = directive.src.charAt(0) === '/' ? directive.src : this.epilogue.player.base_folder + directive.src;
         this.fetchImage(directive.src);
       }
+      for (var k = 0; k < directive.keyframes.length; k++) {
+        var keyframe = directive.keyframes[k];
+        if (keyframe.src && keyframe !== directive) {
+          keyframe.src = keyframe.src.charAt(0) === '/' ? keyframe.src : this.epilogue.player.base_folder + keyframe.src;
+          this.fetchImage(keyframe.src);
+        }
+      }
     }
   }
   this.readyToLoad = true;
@@ -986,7 +984,7 @@ EpiloguePlayer.prototype.hasMoreDirectives = function () {
 }
 
 EpiloguePlayer.prototype.hasPreviousDirectives = function () {
-  return this.sceneIndex > 0|| this.directiveIndex > 0;
+  return this.sceneIndex > 0 || this.directiveIndex > 0;
 }
 
 EpiloguePlayer.prototype.loop = function (timestamp) {
@@ -1411,7 +1409,7 @@ SceneView.prototype.setup = function (scene, sceneIndex, epilogue, lastScene) {
           scene.height = previousScene.height;
           scene.aspectRatio = previousScene.aspectRatio;
         }
-      }  
+      }
     }
   }
 
@@ -1501,7 +1499,9 @@ SceneView.prototype.addBackground = function (background) {
 SceneView.prototype.addImage = function (id, src, args) {
   var img = document.createElement("img");
   img.src = this.assetMap[src].src;
-  this.addSceneObject(new SceneObject(id, img, this, args));
+  var obj = new SceneObject(id, img, this, args);
+  obj.setImage(src);
+  this.addSceneObject(obj);
 }
 
 SceneView.prototype.addSprite = function (directive) {
@@ -1626,15 +1626,15 @@ SceneView.prototype.restoreText = function (directive, context) {
   }
 }
 
-SceneView.prototype.interpolate = function (obj, prop, last, next, t) {
+SceneView.prototype.interpolate = function (obj, prop, last, next, t, mode) {
   var current = obj[prop];
   var start = last[prop];
   var end = next[prop];
-  if (typeof start === "undefined" || isNaN(start) || typeof end === "undefined" || isNaN(end)) {
+  if (mode !== "none" && (typeof start === "undefined" || isNaN(start) || typeof end === "undefined" || isNaN(end))) {
     return;
   }
-  var mode = next.interpolation || "linear";
-  obj[prop] = interpolationModes[mode](prop, start, end, t, last.keyframes, last.index)
+  mode = mode || next.interpolation || "linear";
+  obj[prop] = interpolationModes[mode](prop, start, end, t, last.keyframes, last.index);
 }
 
 SceneView.prototype.updateObject = function (id, last, next, t) {
@@ -1657,6 +1657,7 @@ SceneView.prototype.moveSprite = function (directive, context) {
     context.scalex = sprite.scalex;
     context.scaley = sprite.scaley;
     context.alpha = sprite.alpha;
+    context.src = sprite.src;
     frames.unshift(context);
     context.anim = this.addAnimation(new Animation(directive.id, frames, createClosure(this, this.updateObject), directive.loop, directive.ease, directive.delay, directive.clamp, directive.iterations));
   }
@@ -1683,6 +1684,9 @@ SceneView.prototype.returnSprite = function (directive, context) {
     if (typeof context.alpha !== "undefined") {
       sprite.alpha = context.alpha;
     }
+    if (typeof context.src !== "undefined") {
+      sprite.setImage(context.src);
+    }
     this.removeAnimation(context.anim);
     this.draw();
   }
@@ -1880,7 +1884,7 @@ function SceneObject(id, element, view, args) {
   this.layer = args.layer;
 
   var scene = this.view.scene;
-  
+
   if (element) {
     var vehicle = document.createElement("div");
     vehicle.appendChild(element);
@@ -1971,6 +1975,19 @@ SceneObject.prototype = {
     for (var i = 0; i < this.tweenableProperties.length; i++) {
       this.view.interpolate(this, this.tweenableProperties[i], last, next, t);
     }
+
+    if (next.src) {
+      var oldSrc = this.src;
+      this.view.interpolate(this, "src", last, next, t, "none");
+      if (oldSrc !== this.src) {
+        this.setImage(this.src);
+      }
+    }
+  },
+
+  setImage: function (src) {
+    this.rotElement.src = this.view.assetMap[src].src;
+    this.src = src;
   },
 };