Bug 1426682 - Remove tcanvasmark from talos; r?jmaher draft
authorRob Wood <rwood@mozilla.com>
Thu, 18 Jan 2018 15:29:01 -0500
changeset 722319 4ae30c9f3eb77a58f339d1278b6108687980621e
parent 722297 36a8327139530a95003d431f3f43e7c3f6f9c3ee
child 746599 959171d2a5f7ebb4cfa59ab629ffbacc2150b4b2
push id96134
push userrwood@mozilla.com
push dateThu, 18 Jan 2018 21:47:54 +0000
reviewersjmaher
bugs1426682
milestone59.0a1
Bug 1426682 - Remove tcanvasmark from talos; r?jmaher MozReview-Commit-ID: De1WBDevFay
testing/moz.build
testing/talos/talos.json
testing/talos/talos/output.py
testing/talos/talos/test.py
testing/talos/talos/tests/canvasmark/HelveticaNeueLTStd-Lt.otf
testing/talos/talos/tests/canvasmark/HelveticaNeueLTStd-Md.otf
testing/talos/talos/tests/canvasmark/canvasmark.manifest
testing/talos/talos/tests/canvasmark/images/asteroid1.png
testing/talos/talos/tests/canvasmark/images/asteroid2.png
testing/talos/talos/tests/canvasmark/images/asteroid3.png
testing/talos/talos/tests/canvasmark/images/asteroid4.png
testing/talos/talos/tests/canvasmark/images/bg3_1.jpg
testing/talos/talos/tests/canvasmark/images/canvasmark2013.jpg
testing/talos/talos/tests/canvasmark/images/enemyship1.png
testing/talos/talos/tests/canvasmark/images/fruit.jpg
testing/talos/talos/tests/canvasmark/images/player.png
testing/talos/talos/tests/canvasmark/images/texture5.png
testing/talos/talos/tests/canvasmark/index.html
testing/talos/talos/tests/canvasmark/license.txt
testing/talos/talos/tests/canvasmark/ostrich-black-webfont.woff
testing/talos/talos/tests/canvasmark/scripts/canvasmark_v6.js
testing/talos/talos/tests/canvasmark/scripts/jquery-1.4.2.min.js
testing/talos/talos/tests/canvasmark/scripts/k3d-min.js
testing/talos/talos/tests/canvasmark/scripts/mathlib-min.js
testing/talos/talos/unittests/test_config.py
tools/rewriting/ThirdPartyPaths.txt
--- a/testing/moz.build
+++ b/testing/moz.build
@@ -28,19 +28,16 @@ with Files("talos/talos/tests/v8_7/**"):
     BUG_COMPONENT = ("Core", "Javascript Engine")
 
 with Files("talos/talos/tests/kraken/**"):
     BUG_COMPONENT = ("Core", "Javascript Engine")
 
 with Files("talos/talos/tests/a11y/**"):
     BUG_COMPONENT = ("Core", "Disability Access APIs")
 
-with Files("talos/talos/tests/canvasmark/**"):
-    BUG_COMPONENT = ("Core", "Canvas: 2D")
-
 with Files("talos/talos/tests/webgl/**"):
     BUG_COMPONENT = ("Core", "Canvas: WebGL")
 
 with Files("talos/talos/tests/dromaeo/**"):
     BUG_COMPONENT = ("Core", "DOM")
 
 with Files("talos/talos/tests/svg*"):
     BUG_COMPONENT = ("Core", "SVG")
--- a/testing/talos/talos.json
+++ b/testing/talos/talos.json
@@ -1,16 +1,16 @@
 {
     "suites": {
         "chromez-e10s": {
-            "tests": ["tresize", "tcanvasmark"]
+            "tests": ["tresize"]
         },
         "chromez-stylo-disabled-e10s": {
             "talos_options": ["--disable-stylo"],
-            "tests": ["tresize", "tcanvasmark"]
+            "tests": ["tresize"]
         },
         "dromaeojs-e10s": {
             "tests": ["dromaeo_css", "kraken"]
         },
         "dromaeojs-stylo-disabled-e10s": {
             "talos_options": ["--disable-stylo"],
             "tests": ["dromaeo_css", "kraken"]
         },
--- a/testing/talos/talos/output.py
+++ b/testing/talos/talos/output.py
@@ -250,30 +250,21 @@ class Output(object):
         # the 16 test values, not the sub test values.
         if len(results) != 160:
             raise Exception("Speedometer has 160 subtests, found: %s instead" % len(results))
 
         results = results[9::10]
         score = 60 * 1000 / filter.geometric_mean(results) / correctionFactor
         return score
 
-    @classmethod
-    def CanvasMark_Metric(cls, val_list):
-        """CanvasMark benchmark score (NOTE: this is identical to JS_Metric)"""
-        results = [i for i, j in val_list]
-        LOG.info("CanvasMark benchmark")
-        return sum(results)
-
     def construct_results(self, vals, testname):
         if 'responsiveness' in testname:
             return filter.responsiveness_Metric([val for (val, page) in vals])
         elif testname.startswith('v8_7'):
             return self.v8_Metric(vals)
         elif testname.startswith('kraken'):
             return self.JS_Metric(vals)
-        elif testname.startswith('tcanvasmark'):
-            return self.CanvasMark_Metric(vals)
         elif testname.startswith('speedometer'):
             return self.speedometer_score(vals)
         elif len(vals) > 1:
             return filter.geometric_mean([i for i, j in vals])
         else:
             return filter.mean([i for i, j in vals])
--- a/testing/talos/talos/test.py
+++ b/testing/talos/talos/test.py
@@ -653,35 +653,16 @@ class basic_compositor_video(PageloaderT
                    'docshell.event_starvation_delay_hint': 1,
                    'full-screen-api.warning.timeout': 500,
                    'media.ruin-av-sync.enabled': True}
     filters = filter.ignore_first.prepare(1) + filter.median.prepare()
     unit = 'ms/frame'
     lower_is_better = True
 
 
-@register_test()
-class tcanvasmark(PageloaderTest):
-    """
-    CanvasMark benchmark v0.6
-    """
-    tpmanifest = '${talos}/tests/canvasmark/canvasmark.manifest'
-    win_counters = w7_counters = linux_counters = mac_counters = None
-    tpcycles = 5
-    tppagecycles = 1
-    timeout = 900
-    gecko_profile_interval = 10
-    gecko_profile_entries = 2500000
-    tpmozafterpaint = False
-    preferences = {'dom.send_after_paint_to_content': False}
-    filters = filter.ignore_first.prepare(1) + filter.median.prepare()
-    unit = 'score'
-    lower_is_better = False
-
-
 class dromaeo(PageloaderTest):
     """abstract base class for dramaeo tests"""
     filters = filter.dromaeo.prepare()
     lower_is_better = False
     alert_threshold = 5.0
     tpchrome = False
 
 
deleted file mode 100644
index 1b27e9f67dd3c10b48e7696bef0cb0dd80514094..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 08222549943d74fcf77879f640d6db2b41ad63e8..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
--- a/testing/talos/talos/tests/canvasmark/canvasmark.manifest
+++ /dev/null
@@ -1,3 +0,0 @@
-% http://localhost/tests/canvasmark/index.html?auto=true
-
-
deleted file mode 100644
index 7ebfbbdf5fd7c391ef98db2ed6572bd5c32a8880..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index d3e91df8c6b0574e71db3e887d327f69c12fa052..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index c6cb784440bc5141000cf4c7873c644073312705..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 10b5ea9ab4d518fe7ecbc8d69295240ca69934a8..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 3eba1031c0fe15bc0a2d4762df7aad7cdccdfb79..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 88b9d0b9bd3bab325303d4be19558114f80b4ede..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index e5c849aa1e4020565a5848407ce6d825f329a85e..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 75956fa7e1bcb343b2d161f41c399e5a4d6c2bd0..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index aabf33d99e0257df5897e6d341bceacb656cf24e..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 9795c6b848add733f964c78d945ff25fede762c3..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
--- a/testing/talos/talos/tests/canvasmark/index.html
+++ /dev/null
@@ -1,208 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-   <head>
-      <title>CanvasMark 2013 - HTML5 Canvas 2D Rendering and JavaScript Benchmark</title>
-      <script src="chrome://talos-powers-content/content/TalosContentProfiler.js"></script>
-      <script src="scripts/mathlib-min.js"></script>
-      <script src="scripts/k3d-min.js"></script>
-      <script src="scripts/canvasmark_v6.js"></script>
-      <script async defer src="scripts/jquery-1.4.2.min.js"></script>
-      <style type="text/css">
-@font-face
-{
-	font-family: HelveticaNeue;
-	src: url('HelveticaNeueLTStd-Lt.otf');
-}
-@font-face
-{
-	font-family: HelveticaNeueBold;
-	src: url('HelveticaNeueLTStd-Md.otf');
-}
-@font-face
-{
-	font-family: OstrichSans;
-	src: url('ostrich-black-webfont.woff');
-}
-body
-{
-   background-color: #aaa;
-   font-family: HelveticaNeue, Helvetica;
-   color: #333;
-}
-.title
-{
-   font-family: OstrichSans, Helvetica;
-   padding-left: 20px;
-   padding-top: 6px;
-   color: #000;
-   font-size: 1.75em;
-}
-P
-{
-   font-size: 1em;
-   padding-left: 20px;
-   padding-top: 0;
-   padding-bottom: 0;
-   line-height: 1.5em;
-}
-P.left
-{
-   padding-left: 2em;
-}
-a, a:visited, a:active, a:hover
-{
-   color: #225588;
-   text-decoration: none;
-}
-a:hover
-{
-   text-decoration: underline;
-}
-
-.box-shadow
-{
-   box-shadow: 0px 3px 16px #222;
-}
-
-.wrapper
-{
-   padding-top: 12px;
-   text-align: center;
-}
-
-.info
-{
-   display: inline-block;
-   margin-left: 1.5em;
-   text-align: left;
-   vertical-align: top;
-}
-
-.infopanel
-{
-   background-color: #F0F0FF;
-   display: inline-block;
-   line-height: 2em;
-   padding: 1.5em;
-   width: 28em;
-}
-
-canvas
-{
-   border: 1px solid #333;
-   background-color: #000;
-}
-
-.results
-{
-}
-
-.results-wrapper
-{
-   display: none;
-   border: 1px solid #aaa;
-   background-color: #ddd;
-   margin-left: 1em;
-}
-
-.prose
-{
-   font-family: HelveticaNeueBold, Helvetica;
-   font-size: 0.85em;
-   line-height: 1.4em;
-}
-
-.footer
-{
-   margin-top: 16px;
-   text-align: center;
-}
-
-.small
-{
-   background-color: #eee;
-   display: inline-block;
-   font-family: HelveticaNeueBold, Helvetica;
-   font-size: 0.85em;
-   padding: 6px 12px;
-   text-align: center;
-}
-
-.button-overlay
-{
-   position: absolute;
-   right: 1em;
-   bottom: 1em;
-}
-
-.toggle-area
-{
-   display: none;
-}
-
-a.toggler,
-a.toggler:visited,
-a.toggler:hover,
-a.toggler:active
-{
-   text-decoration: none !important;
-}
-      </style>
-      <meta name="description" content="CanvasMark - HTML5 Canvas 2D Rendering and JavaScript Benchmark by Kevin Roast" />
-   </head>
-   
-   <body>
-      <div class="button-overlay">
-         <!-- AddThis Button BEGIN -->
-         <div class="addthis_toolbox addthis_default_style addthis_32x32_style">
-         <a class="addthis_button_preferred_1"></a>
-         <a class="addthis_button_preferred_2"></a>
-         <a class="addthis_button_compact"></a>
-         <a class="addthis_counter addthis_bubble_style"></a>
-         </div>
-         <!-- AddThis Button END -->
-      </div>
-      <div class="wrapper">
-         <canvas class="box-shadow" id="canvas" width="640" height="640"></canvas>
-         <div class="info">
-            <div class="infopanel">
-               <div class="title">CanvasMark 2013 - HTML5 Canvas 2D Rendering and JavaScript Benchmark</div>
-               <div>
-                  <p>Tests the HTML5 &lt;canvas&gt; rendering performance for commonly used operations in HTML5 games: bitmaps, canvas drawing, alpha blending, polygon fills, shadows and text functions.</p>
-               </div>
-               <div id="results-wrapper" class="results-wrapper">
-                  <div class="results" id="results"></div>
-                  <div class="results"><p><a id="tweetlink" href="#" target="new">Tweet this result.</a></p></div>
-               </div>
-               <div>
-                  <p>Run benchmark using HTML5 compatible browser: <a href="http://www.google.co.uk/chrome" target="new">Chrome</a> | <a href="http://www.mozilla.com/firefox" target="new">FireFox</a> | <a href="http://www.apple.com/safari/download" target="new">Safari</a> | <a href="http://www.opera.com/download" target="new">Opera</a> | <a href="http://windows.microsoft.com/en-US/internet-explorer/products/ie/home" target="new">IE9/10</a>.</p>
-               </div>
-               <div>
-                  <p>This benchmark suite uses a number of elements from my HTML5 games including <a href="../asteroids">Asteroids</a> and <a href="../arena5">Arena5</a>. See more <a href="../index.html">HTML5 Canvas experiments</a>.</p>
-               </div>
-               <div>
-                  <p class="prose">Important notes for Windows + Chrome users! <a class="toggler" title="More..." href="#" onclick="$('#toggle-1').toggle();return false;">[+]</a>
-                  <span id="toggle-1" class="toggle-area"><br>To get the best benchmark score for your machine, it is advisable to Disable VSync. Go to &quot;about:flags&quot; and toggle: <code>Disable GPU VSync &quot;Disables synchronisation with the display's vertical refresh rate when GPU rendering.&quot;</code> This will resolve the issue with the Chrome implementation of &quot;requestAnimationFrame()&quot; that tries to maintain a steady 60 frames-per-second (FPS) but on Windows with accelerated 2D canvas support, it will drop immediately down to 30 FPS when 60 FPS is not achievable with no gradual degredation. On Mac/Linux the drop in FPS is gradual and therefore does not affect the benchmark. So if you see the FPS counter drop directly from around 60 FPS to 30 FPS then you should do this. This will not produce an &quot;unfair&quot; score as scores are based on time not the number of frames generated.</span>
-                  </p>
-               </div>
-               <div>
-                  <p class="prose">How to interpret the results <a class="toggler" title="More..." href="#" onclick="$('#toggle-2').toggle();return false;">[+]</a>
-                     <span id="toggle-2" class="toggle-area"><br>CanvasMark gives a score for the browser based on the combined performance in each of the various stress tests. You should ensure the browser window is not minimized and that no other CPU or GPU intensive processes are running during the test. The results can only be compared to other browsers running on the same machine - as each machine with different CPU or graphics will produce difference results.</span>
-                  </p>
-               </div>
-               <div>
-                  <p class="prose">How does it work <a class="toggler" title="More..." href="#" onclick="$('#toggle-3').toggle();return false;">[+]</a>
-                     <span id="toggle-3" class="toggle-area"><br>Each test is run and progressively tuned to stress the browser until a steady 30 frames-per-second (FPS) is reached. More objects are added to the scene or the scene is made more complex to render until that point is reached. Then the test is considered completed and the next test is started. The score is based on the length of time the browser was able to maintain the test scene at greater than 30 FPS, multiplied by a weighting for the complexity of each test type.</span>
-                  </p>
-               </div>
-               <div>
-                  <p class="prose">Benchmark version 1.1 [25-03-2013]</p>
-               </div>
-            </div>
-         </div>
-      </div>
-      <div class="footer">
-         <div class="small">Follow CanvasMark <a href="http://twitter.com/canvasbench" target="new">on Twitter</a> or just <a href="http://twitter.com/home/?status=CanvasMark%20-%20HTML5%20Canvas%20performance%20and%20benchmarking%20tool:%20http://bit.ly/canvasmark%20%23javascript%20%23html5%20(@canvasbench)" target="new">Tweet this.</a> Last updated: 25th March 2013 by Kevin Roast</div>
-      </div>
-   </body>
-</html>
deleted file mode 100644
--- a/testing/talos/talos/tests/canvasmark/license.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-Copyright (C) 2013 Kevin Roast kevtoast@yahoo.com
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-A "link back" to the original holding website "www.kev3d.co.uk" or a reference
-to the original author "Kevin Roast" shall be provided on any copies or
-substantial portions of the Software.
-
-Except as contained in this notice, the name(s) of the above copyright holders 
-shall not be used in advertising or otherwise to promote the sale, use or other
-dealings in this Software without prior written authorization.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
\ No newline at end of file
deleted file mode 100644
index 40473ec33071bc78141ad1f3d5ccb22db60b9345..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
--- a/testing/talos/talos/tests/canvasmark/scripts/canvasmark_v6.js
+++ /dev/null
@@ -1,6490 +0,0 @@
-/**
- * Game class library, utility functions and globals.
- * 
- * @author Kevin Roast
- * 
- * 30/04/09 Initial version.
- * 12/05/09 Refactored to remove globals into GameHandler instance and added FPS controller game loop.
- * 17/01/11 Full screen resizable canvas
- * 26/01/11 World to screen transformation - no longer unit=pixel
- * 03/08/11 Modified version for CanvasMark usage
- */
-
-var KEY = { SHIFT:16, CTRL:17, ESC:27, RIGHT:39, UP:38, LEFT:37, DOWN:40, SPACE:32,
-            A:65, D:68, E:69, G:71, L:76, P:80, R:82, S:83, W:87, Z:90, OPENBRACKET:219, CLOSEBRACKET:221 };
-var iOS = (navigator.userAgent.indexOf("iPhone;") != -1 ||
-           navigator.userAgent.indexOf("iPod;") != -1 ||
-           navigator.userAgent.indexOf("iPad;") != -1);
-
-/**
- * Game Handler.
- * 
- * Singleton instance responsible for managing the main game loop and
- * maintaining a few global references such as the canvas and frame counters.
- */
-var GameHandler =
-{
-   /**
-    * The single Game.Main derived instance
-    */
-   game: null,
-   
-   /**
-    * True if the game is in pause state, false if running
-    */
-   paused: false,
-   
-   /**
-    * The single canvas play field element reference
-    */
-   canvas: null,
-   
-   /**
-    * Width of the canvas play field
-    */
-   width: 0,
-   
-   /**
-    * Height of the canvas play field
-    */
-   height: 0,
-   
-   offsetX: 0,
-   
-   offsetY: 0,
-   
-   /**
-    * Frame counter
-    */
-   frameCount: 0,
-   
-   sceneStartTime: 0,
-   benchmarkScoreCount: 0,
-   benchmarkScores: [],
-   benchmarkLabels: [],
-
-   FPSMS: 60,
-   FRAME_TIME_MAX: 1000/30,
-   MAX_GLITCH_COUNT: 10,
-   
-   /**
-    * Debugging output
-    */
-   maxfps: 0,
-   frametime: 0,
-   frameInterval: 0,
-   
-   /**
-    * Init function called once by your window.onload handler
-    */
-   init: function()
-   {
-      this.canvas = document.getElementById('canvas');
-      this.width = this.canvas.height;
-      this.height = this.canvas.width;
-      
-      var me = GameHandler;
-      var el = me.canvas, x = 0, y = 0; 
-      do
-      {
-         y += el.offsetTop;
-         x += el.offsetLeft;
-      } while (el = el.offsetParent);
-      
-      // compute canvas offset including page view position
-      me.offsetX = x - window.pageXOffset;
-      me.offsetY = y - window.pageYOffset;
-   },
-   
-   /**
-    * Game start method - begins the main game loop.
-    * Pass in the object that represent the game to execute.
-    * Also called each frame by the main game loop unless paused.
-    * 
-    * @param {Game.Main} game main derived object handler
-    */
-   start: function(game)
-   {
-      if (game) this.game = game;
-      GameHandler.game.frame();
-   },
-   
-   /**
-    * Game pause toggle method.
-    */
-   pause: function()
-   {
-      if (this.paused)
-      {
-         this.paused = false;
-         GameHandler.game.frame();
-      }
-      else
-      {
-         this.paused = true;
-      }
-   }
-};
-
-
-/**
- * Game root namespace.
- *
- * @namespace Game
- */
-if (typeof Game == "undefined" || !Game)
-{
-   var Game = {};
-}
-
-
-/**
- * Transform a vector from world coordinates to screen
- * 
- * @method worldToScreen
- * @return Vector or null if non visible
- */
-Game.worldToScreen = function worldToScreen(vector, world, radiusx, radiusy)
-{
-   // transform a vector from the world to the screen
-   radiusx = (radiusx ? radiusx : 0);
-   radiusy = (radiusy ? radiusy : radiusx);
-   var screenvec = null,
-       viewx = vector.x - world.viewx,
-       viewy = vector.y - world.viewy;
-   if (viewx < world.viewsize + radiusx && viewy < world.viewsize + radiusy &&
-       viewx > -radiusx && viewy > -radiusy)
-   {
-      screenvec = new Vector(viewx, viewy).scale(world.scale);
-   }
-   return screenvec;
-};
-
-
-/**
- * Game main loop class.
- * 
- * @namespace Game
- * @class Game.Main
- */
-(function()
-{
-   Game.Main = function()
-   {
-      var me = this;
-      
-      document.onkeydown = function(event)
-      {
-         var keyCode = (event === null ? window.event.keyCode : event.keyCode);
-         
-         if (me.sceneIndex !== -1)
-         {
-            if (me.scenes[me.sceneIndex].onKeyDownHandler(keyCode))
-            {
-               // if the key is handled, prevent any further events
-               if (event)
-               {
-                  event.preventDefault();
-                  event.stopPropagation();
-               }
-            }
-         }
-      };
-      
-      document.onkeyup = function(event)
-      {
-         var keyCode = (event === null ? window.event.keyCode : event.keyCode);
-         
-         if (me.sceneIndex !== -1)
-         {
-            if (me.scenes[me.sceneIndex].onKeyUpHandler(keyCode))
-            {
-               // if the key is handled, prevent any further events
-               if (event)
-               {
-                  event.preventDefault();
-                  event.stopPropagation();
-               }
-            }
-         }
-      };
-   };
-   
-   Game.Main.prototype =
-   {
-      scenes: [],
-      
-      startScene: null,
-      
-      endScene: null,
-      
-      currentScene: null,
-      
-      sceneIndex: -1,
-      
-      lastFrameStart: 0,
-      
-      interval: null,
-      
-      /**
-       * Game frame method - called by window timeout.
-       */
-      frame: function frame()
-      {
-         var frameStart = Date.now();
-         GameHandler.frameInterval = frameStart - GameHandler.frameStart;
-         if (GameHandler.frameInterval === 0) GameHandler.frameInterval = 1;
-         
-         // calculate scene transition and current scene
-         var currentScene = this.currentScene;
-         if (currentScene === null)
-         {
-            // set to scene zero (game init)
-            this.sceneIndex = 0;
-            currentScene = this.scenes[0];
-            currentScene._onInitScene();
-            currentScene.onInitScene();
-         }
-         
-         if ((currentScene.interval === null || currentScene.interval.complete) && currentScene.isComplete())
-         {
-            if (this.sceneIndex === 0)
-            {
-               // reset total score recorded during the benchmark
-               GameHandler.benchmarkScoreCount = 0;
-            }
-            this.sceneIndex++;
-            if (this.sceneIndex < this.scenes.length)
-            {
-               currentScene = this.scenes[this.sceneIndex];
-            }
-            else
-            {
-               this.sceneIndex = 0;
-               currentScene = this.scenes[0];
-            }
-            currentScene._onInitScene();
-            currentScene.onInitScene();
-         }
-         
-         // get canvas context for a render pass
-         var ctx = GameHandler.canvas.getContext('2d');
-         
-         // calculate viewport transform and offset against the world
-         // we want to show a fixed number of world units in our viewport
-         // so calculate the scaling factor to transform world to view
-         currentScene.world.scale = GameHandler.width / currentScene.world.viewsize;
-         
-         // render the game and current scene
-         if (currentScene.interval === null || currentScene.interval.complete)
-         {
-            currentScene.onBeforeRenderScene(currentScene._onBeforeRenderScene());
-            currentScene.onRenderScene(ctx);
-         }
-         else
-         {
-            // for the benchmark we just clear the canvas
-            ctx.clearRect(0, 0, GameHandler.width, GameHandler.height);
-            currentScene.interval.intervalRenderer.call(currentScene, currentScene.interval, ctx);
-         }
-         
-         // update global frame counter and current scene reference
-         this.currentScene = currentScene;
-         GameHandler.frameCount++;
-         
-         // calculate frame time and frame multiplier required for smooth animation
-         var now = Date.now();
-         GameHandler.frametime = now - frameStart;
-         GameHandler.frameMultipler = GameHandler.frameInterval / GameHandler.FPSMS;
-         GameHandler.frameStart = frameStart;
-         
-         // update last fps every few frames for debugging output
-         if (GameHandler.frameCount % 16 === 0) GameHandler.lastfps = ~~(1000 / GameHandler.frameInterval);
-         
-         // IE9 does not support requestAnimationFrame so need to calc interval manually
-         var ieinterval = 17 - (GameHandler.frametime);
-         
-         requestAnimFrame(GameHandler.start, ieinterval > 0 ? ieinterval : 1);
-      },
-      
-      isGameOver: function isGameOver()
-      {
-         return false;
-      }
-   };
-})();
-
-// requestAnimFrame shim
-window.requestAnimFrame = (function()
-{
-   return  window.requestAnimationFrame       || 
-           window.webkitRequestAnimationFrame || 
-           window.oRequestAnimationFrame      || 
-           window.mozRequestAnimationFrame    || 
-           window.msRequestAnimationFrame     || 
-           function(callback, frameOffset)
-           {
-               window.setTimeout(callback, frameOffset);
-           };
-})();
-
-
-/**
- * Game scene base class. Adapted for Benchmark scoring.
- * 
- * @namespace Game
- * @class Game.Scene
- */
-(function()
-{
-   Game.Scene = function(playable, interval)
-   {
-      this.playable = playable;
-      this.interval = interval;
-   };
-   
-   Game.Scene.prototype =
-   {
-      playable: true,
-      
-      interval: null,
-
-      sceneStartTime: null,
-      sceneCompletedTime: null,
-      sceneGlitchCount: 0,
-      
-      testState: 0,
-      testScore: 0,
-      
-      world:
-      {
-         size: 1500,       // total units vertically and horizontally
-         viewx: 0,         // current view left corner xpos
-         viewy: 0,         // current view left corner ypos
-         viewsize: 1500,   // size of the viewable area
-         scale: 1  // scale for world->view transformation - calculated based on physical viewport size
-      },
-      
-      /**
-       * Return true if this scene should update the actor list.
-       */
-      isPlayable: function isPlayable()
-      {
-         return this.playable;
-      },
-      
-      _onInitScene: function _onInitScene()
-      {
-         this.sceneGlitchCount = this.testScore = this.testState = 0;
-         this.sceneStartTime = Date.now();
-         this.sceneCompletedTime = null;
-      },
-
-      onInitScene: function onInitScene()
-      {
-         if (this.interval !== null)
-         {
-            // reset interval flag
-            this.interval.reset();
-         }
-      },
-      
-      _onBeforeRenderScene: function _onBeforeRenderScene()
-      {
-         // calculate if the scene shoud render in benchmark mode or not
-         if (this.playable)
-         {
-            if (!this.sceneCompletedTime)
-            {
-               if (GameHandler.frameInterval > GameHandler.FRAME_TIME_MAX)
-               {
-                  this.sceneGlitchCount++;
-               }
-               if (this.sceneGlitchCount < GameHandler.MAX_GLITCH_COUNT)
-               {
-                  return true;
-               }
-               else
-               {
-                  // too many FPS glitches! so benchmark scene completed (allow to run visually for a few seconds)
-                  this.sceneCompletedTime = Date.now();
-                  var score = ~~(((this.sceneCompletedTime - this.sceneStartTime) * this.testScore) / 100);
-                  GameHandler.benchmarkScoreCount += score;
-                  GameHandler.benchmarkScores.push(score);
-                  var name = this.interval.label.replace(/Test [0-9] - /g, "");
-                  name = name.replace(/, /g, "- ");
-                  GameHandler.benchmarkLabels.push(name);
-                  if (typeof console !== "undefined")
-                  {
-                     console.log(score + " [" + this.interval.label + "]");
-                  }
-               }
-            }
-         }
-         return false;
-      },
-      
-      getTransientTestScore: function getTransientTestScore()
-      {
-         var score = ((this.sceneCompletedTime ? this.sceneCompletedTime : Date.now()) - this.sceneStartTime) * this.testScore;
-         return ~~(score/100);
-      },
-      
-      onBeforeRenderScene: function onBeforeRenderScene()
-      {
-      },
-      
-      onRenderScene: function onRenderScene(ctx)
-      {
-      },
-      
-      onRenderInterval: function onRenderInterval(ctx)
-      {
-      },
-      
-      onKeyDownHandler: function onKeyDownHandler(keyCode)
-      {
-      },
-      
-      onKeyUpHandler: function onKeyUpHandler(keyCode)
-      {
-      },
-      
-      isComplete: function isComplete()
-      {
-         return this.sceneCompletedTime && (Date.now() > this.sceneCompletedTime);
-      },
-      
-      intervalRenderer: function intervalRenderer(interval, ctx)
-      {
-         if (interval.framecounter++ < 100)
-         {
-            Game.centerFillText(ctx, interval.label, "14pt Courier New", GameHandler.height/2 - 8, "white");
-         }
-         else
-         {
-            interval.complete = true;
-         }
-      }
-   };
-})();
-
-
-(function()
-{
-   Game.Interval = function(label, intervalRenderer)
-   {
-      this.label = label;
-      this.intervalRenderer = intervalRenderer;
-      this.framecounter = 0;
-      this.complete = false;
-   };
-   
-   Game.Interval.prototype =
-   {
-      label: null,
-      intervalRenderer: null,
-      framecounter: 0,
-      complete: false,
-      
-      reset: function reset()
-      {
-         this.framecounter = 0;
-         this.complete = false;
-      }
-   };
-})();
-
-
-/**
- * Actor base class.
- * 
- * Game actors have a position in the game world and a current vector to indicate
- * direction and speed of travel per frame. They each support the onUpdate() and
- * onRender() event methods, finally an actor has an expired() method which should
- * return true when the actor object should be removed from play.
- * 
- * An actor can be hit and destroyed by bullets or similar. The class supports a hit()
- * method which should return true when the actor should be removed from play.
- * 
- * @namespace Game
- * @class Game.Actor
- */
-(function()
-{
-   Game.Actor = function(p, v)
-   {
-      this.position = p;
-      this.vector = v;
-      
-      return this;
-   };
-   
-   Game.Actor.prototype =
-   {
-      /**
-       * Actor position
-       *
-       * @property position
-       * @type Vector
-       */
-      position: null,
-      
-      /**
-       * Actor vector
-       *
-       * @property vector
-       * @type Vector
-       */
-      vector: null,
-      
-      /**
-       * Alive flag
-       *
-       * @property alive
-       * @type boolean
-       */
-      alive: true,
-      
-      /**
-       * Radius - default is zero to imply that it is not affected by collision tests etc.
-       *
-       * @property radius
-       * @type int
-       */
-      radius: 0,
-      
-      /**
-       * Actor expiration test
-       * 
-       * @method expired
-       * @return true if expired and to be removed from the actor list, false if still in play
-       */
-      expired: function expired()
-      {
-         return !(this.alive);
-      },
-      
-      /**
-       * Hit by bullet
-       * 
-       * @param force of the impacting bullet (as the actor may support health)
-       * @return true if destroyed, false otherwise
-       */
-      hit: function hit(force)
-      {
-         this.alive = false;
-         return true;
-      },
-      
-      /**
-       * Transform current position vector from world coordinates to screen.
-       * Applies the appropriate translation and scaling to the canvas context.
-       * 
-       * @method worldToScreen
-       * @return Vector or null if non visible
-       */
-      worldToScreen: function worldToScreen(ctx, world, radius)
-      {
-         var viewposition = Game.worldToScreen(this.position, world, radius);
-         if (viewposition)
-         {
-            // scale ALL graphics... - translate to position apply canvas scaling
-            ctx.translate(viewposition.x, viewposition.y);
-            ctx.scale(world.scale, world.scale);
-         }
-         return viewposition;
-      },
-      
-      /**
-       * Actor game loop update event method. Called for each actor
-       * at the start of each game loop cycle.
-       * 
-       * @method onUpdate
-       */
-      onUpdate: function onUpdate()
-      {
-      },
-      
-      /**
-       * Actor rendering event method. Called for each actor to
-       * render for each frame.
-       * 
-       * @method onRender
-       * @param ctx {object} Canvas rendering context
-       * @param world {object} World metadata
-       */
-      onRender: function onRender(ctx, world)
-      {
-      }
-   };
-})();
-
-
-/**
- * SpriteActor base class.
- * 
- * An actor that can be rendered by a bitmap. The sprite handling code deals with the increment
- * of the current frame within the supplied bitmap sprite strip image, based on animation direction,
- * animation speed and the animation length before looping. Call renderSprite() each frame.
- * 
- * NOTE: by default sprites source images are 64px wide 64px by N frames high and scaled to the
- * appropriate final size. Any other size input source should be set in the constructor.
- * 
- * @namespace Game
- * @class Game.SpriteActor
- */
-(function()
-{
-   Game.SpriteActor = function(p, v, s)
-   {
-      Game.SpriteActor.superclass.constructor.call(this, p, v);
-      if (s) this.frameSize = s;
-      
-      return this;
-   };
-   
-   extend(Game.SpriteActor, Game.Actor,
-   {
-      /**
-       * Size in pixels of the width/height of an individual frame in the image
-       */
-      frameSize: 64,
-      
-      /**
-       * Animation image sprite reference.
-       * Sprite image sources are all currently 64px wide 64px by N frames high.
-       */
-      animImage: null,
-      
-      /**
-       * Length in frames of the sprite animation
-       */
-      animLength: 0,
-      
-      /**
-       * Animation direction, true for forward, false for reverse.
-       */
-      animForward: true,
-      
-      /**
-       * Animation frame inc/dec speed.
-       */
-      animSpeed: 1.0,
-      
-      /**
-       * Current animation frame index
-       */
-      animFrame: 0,
-      
-      /**
-       * Render sprite graphic based on current anim image, frame and anim direction
-       * Automatically updates the current anim frame.
-       * 
-       * Optionally this method will automatically correct for objects moving on/off
-       * a cyclic canvas play area - if so it will render the appropriate stencil
-       * sections of the sprite top/bottom/left/right as needed to complete the image.
-       * Note that this feature can only be used if the sprite is absolutely positioned
-       * and not translated/rotated into position by canvas operations.
-       */
-      renderSprite: function renderSprite(ctx, x, y, w, cyclic)
-      {
-         var offset = this.animFrame << 6,
-             fs = this.frameSize;
-         
-         ctx.drawImage(this.animImage, 0, offset, fs, fs, x, y, w, w);
-         
-         if (cyclic)
-         {
-            if (x < 0 || y < 0)
-            {
-               ctx.drawImage(this.animImage, 0, offset, fs, fs,
-                  (x < 0 ? (GameHandler.width + x) : x),
-                  (y < 0 ? (GameHandler.height + y) : y),
-                  w, w);
-            }
-            if (x + w >= GameHandler.width || y + w >= GameHandler.height)
-            {
-               ctx.drawImage(this.animImage, 0, offset, fs, fs,
-                  (x + w >= GameHandler.width ? (x - GameHandler.width) : x),
-                  (y + w >= GameHandler.height ? (y - GameHandler.height) : y),
-                  w, w);
-            }
-         }
-         
-         // update animation frame index
-         if (this.animForward)
-         {
-            this.animFrame += this.animSpeed;
-            if (this.animFrame >= this.animLength)
-            {
-               this.animFrame = 0;
-            }
-         }
-         else
-         {
-            this.animFrame -= this.animSpeed;
-            if (this.animFrame < 0)
-            {
-               this.animFrame = this.animLength - 1;
-            }
-         }
-      }
-   });
-})();
-
-
-/**
- * EffectActor base class.
- * 
- * An actor representing a transient effect in the game world. An effect is nothing more than
- * a special graphic that does not play any direct part in the game and does not interact with
- * any other objects. It automatically expires after a set lifespan, generally the rendering of
- * the effect is based on the remaining lifespan.
- * 
- * @namespace Game
- * @class Game.EffectActor
- */
-(function()
-{
-   Game.EffectActor = function(p, v, lifespan)
-   {
-      Game.EffectActor.superclass.constructor.call(this, p, v);
-      this.lifespan = lifespan;
-      return this;
-   };
-   
-   extend(Game.EffectActor, Game.Actor,
-   {
-      /**
-       * Effect lifespan remaining
-       */
-      lifespan: 0,
-      
-      /**
-       * Actor expiration test
-       * 
-       * @return true if expired and to be removed from the actor list, false if still in play
-       */
-      expired: function expired()
-      {
-      	// deduct lifespan from the explosion
-      	return (--this.lifespan === 0);
-      }
-   });
-})();
-
-
-/**
- * Image Preloader class. Executes the supplied callback function once all
- * registered images are loaded by the browser.
- * 
- * @namespace Game
- * @class Game.Preloader
- */
-(function()
-{
-   Game.Preloader = function()
-   {
-      this.images = [];
-      return this;
-   };
-   
-   Game.Preloader.prototype =
-   {
-      /**
-       * Image list
-       *
-       * @property images
-       * @type Array
-       */
-      images: null,
-      
-      /**
-       * Callback function
-       *
-       * @property callback
-       * @type Function
-       */
-      callback: null,
-      
-      /**
-       * Images loaded so far counter
-       */
-      counter: 0,
-      
-      /**
-       * Add an image to the list of images to wait for
-       */
-      addImage: function addImage(img, url)
-      {
-         var me = this;
-         img.url = url;
-         // attach closure to the image onload handler
-         img.onload = function()
-         {
-            me.counter++;
-            if (me.counter === me.images.length)
-            {
-               // all images are loaded - execute callback function
-               me.callback.call(me);
-            }
-         };
-         this.images.push(img);
-      },
-      
-      /**
-       * Load the images and call the supplied function when ready
-       */
-      onLoadCallback: function onLoadCallback(fn)
-      {
-         this.counter = 0;
-         this.callback = fn;
-         // load the images
-         for (var i=0, j=this.images.length; i<j; i++)
-         {
-            this.images[i].src = this.images[i].url;
-         }
-      }
-   };
-})();
-
-
-/**
- * Render text into the canvas context.
- * Compatible with FF3, FF3.5, SF4, GC4, OP10
- * 
- * @method Game.drawText
- * @static
- */
-Game.drawText = function(g, txt, font, x, y, col)
-{
-   g.save();
-   if (col) g.strokeStyle = col;
-   g.font = font;
-   g.strokeText(txt, x, y);
-   g.restore();
-};
-
-Game.fillText = function(g, txt, font, x, y, col)
-{
-   g.save();
-   if (col) g.fillStyle = col;
-   g.font = font;
-   g.fillText(txt, x, y);
-   g.restore();
-};
-
-Game.centerFillText = function(g, txt, font, y, col)
-{
-   g.save();
-   if (col) g.fillStyle = col;
-   g.font = font;
-   g.fillText(txt, (GameHandler.width - g.measureText(txt).width) / 2, y);
-   g.restore();
-};
-
-Game.fontSize = function fontSize(world, size)
-{
-   var s = ~~(size * world.scale * 2);
-   if (s > 20) s = 20;
-   else if (s < 8) s = 8;
-   return s;
-};
-
-Game.fontFamily = function fontFamily(world, size, font)
-{
-   return Game.fontSize(world, size) + "pt " + (font ? font : "Courier New");
-};/**
- * Feature test scenes for CanvasMark Rendering Benchmark - March 2013
- *  
- * (C) 2013 Kevin Roast kevtoast@yahoo.com @kevinroast
- * 
- * Please see: license.txt
- * You are welcome to use this code, but I would appreciate an email or tweet
- * if you do anything interesting with it!
- */
-
-
-/**
- * Feature root namespace.
- * 
- * @namespace Feature
- */
-if (typeof Feature == "undefined" || !Feature)
-{
-   var Feature = {};
-}
-
-Feature.textureImage = new Image();
-Feature.blurImage = new Image();
-
-/**
- * Feature main Benchmark Test class.
- * 
- * @namespace Feature
- * @class Feature.Test
- */
-(function()
-{
-   Feature.Test = function(benchmark, loader)
-   {
-      loader.addImage(Feature.textureImage, "./images/texture5.png");
-      loader.addImage(Feature.blurImage, "./images/fruit.jpg");
-
-      // add benchmark scenes
-      var t = benchmark.scenes.length;
-      for (var i=0; i<3; i++)
-      {
-         benchmark.addBenchmarkScene(new Feature.GameScene(this, t+i, i));
-      }
-   };
-   
-   Feature.Test.prototype =
-   {
-   };
-})();
-
-
-(function()
-{
-   /**
-    * Feature.K3DController constructor
-    */
-   Feature.K3DController = function()
-   {
-      Feature.K3DController.superclass.constructor.call(this);
-   };
-   
-   extend(Feature.K3DController, K3D.BaseController,
-   {
-      /**
-       * Render tick - should be called from appropriate scene renderer
-       */
-      render: function(ctx)
-      {
-         // execute super class method to process render pipelines
-         ctx.save();
-         ctx.translate(GameHandler.width/2, GameHandler.height/2);
-         this.processFrame(ctx);
-         ctx.restore();
-      }
-   });
-})();
-
-
-/**
- * Feature Game scene class.
- * 
- * @namespace Feature
- * @class Feature.GameScene
- */
-(function()
-{
-   Feature.GameScene = function(game, test, feature)
-   {
-      this.game = game;
-      this.test = test;
-      this.feature = feature;
-      
-      var msg = "Test " + test + " - ";
-      switch (feature)
-      {
-         case 0: msg += "Plasma - Maths, canvas shapes"; break;
-         case 1: msg += "3D Rendering - Maths, polygons, image transforms"; break;
-         case 2: msg += "Pixel blur - Math, getImageData, putImageData"; break;
-      }
-      var interval = new Game.Interval(msg, this.intervalRenderer);
-      Feature.GameScene.superclass.constructor.call(this, true, interval);
-   };
-   
-   extend(Feature.GameScene, Game.Scene,
-   {
-      feature: 0,
-      index: 0,
-      game: null,
-      
-      /**
-       * Scene init event handler
-       */
-      onInitScene: function onInitScene()
-      {
-         switch (this.feature)
-         {
-            case 0:
-            {
-               // generate plasma palette
-               var palette = [];
-               for (var i=0,r,g,b; i<256; i++)
-               {
-                  r = ~~(128 + 128 * Math.sin(Math.PI * i / 32));
-                  g = ~~(128 + 128 * Math.sin(Math.PI * i / 64));
-                  b = ~~(128 + 128 * Math.sin(Math.PI * i / 128));
-                  palette[i] = "rgb(" + ~~r + "," + ~~g + "," + ~~b + ")";
-               }
-               this.paletteoffset = 0;
-               this.palette = palette;
-               
-               // size of the plasma pixels ratio - bigger = more calculations and rendering
-               this.plasmasize = 8;
-               
-               this.testScore = 10;
-               
-               break;
-            }
-            
-            case 1:
-            {
-               // K3D controller
-               this.k3d = new Feature.K3DController();
-               // generate 3D objects
-               for (var i=0; i<10; i++)
-               {
-                  this.add3DObject(i);
-               }
-               
-               this.testScore = 10;
-               
-               break;
-            }
-            
-            case 2:
-            {
-               this.testScore = 25;
-               break;
-            }
-         }
-      },
-
-      add3DObject: function add3DObject(offset)
-      {
-         var gap = 360/20;
-         var obj = new K3D.K3DObject();
-         obj.ophi = (360 / gap) * offset;
-         obj.otheta = (180 / gap / 2) * offset;
-         obj.textures.push(Feature.textureImage);
-         with (obj)
-         {
-            drawmode = "solid";     // one of "point", "wireframe", "solid"
-            shademode = "lightsource";    // one of "plain", "depthcue", "lightsource" (solid drawing mode only)
-            addgamma = 0.5; addtheta = -1.0; addphi = -0.75;
-            aboutx = 150; abouty = -150; aboutz = -50;
-            perslevel = 512;
-            scale = 13;
-            init(
-               // describe the points of a simple unit cube
-               [{x:-1,y:1,z:-1}, {x:1,y:1,z:-1}, {x:1,y:-1,z:-1}, {x:-1,y:-1,z:-1}, {x:-1,y:1,z:1}, {x:1,y:1,z:1}, {x:1,y:-1,z:1}, {x:-1,y:-1,z:1}],
-               // describe the edges of the cube
-               [{a:0,b:1}, {a:1,b:2}, {a:2,b:3}, {a:3,b:0}, {a:4,b:5}, {a:5,b:6}, {a:6,b:7}, {a:7,b:4}, {a:0,b:4}, {a:1,b:5}, {a:2,b:6}, {a:3,b:7}],
-               // describe the polygon faces of the cube
-               [{color:[255,0,0],vertices:[0,1,2,3],texture:0},{color:[0,255,0],vertices:[0,4,5,1]},{color:[0,0,255],vertices:[1,5,6,2]},{color:[255,255,0],vertices:[2,6,7,3]},{color:[0,255,255],vertices:[3,7,4,0]},{color:[255,0,255],vertices:[7,6,5,4],texture:0}]
-            );
-         }
-         // add another 3D object to the controller
-         this.k3d.addK3DObject(obj);
-      },
-      
-      /**
-       * Scene before rendering event handler
-       */
-      onBeforeRenderScene: function onBeforeRenderScene(benchmark)
-      {
-         if (benchmark)
-         {
-            switch (this.feature)
-            {
-               case 0:
-               {
-                  if (Date.now() - this.sceneStartTime > this.testState)
-                  {
-                     this.testState+=100;
-                     this.plasmasize++;
-                  }
-                  break;
-               }
-               case 1:
-               {
-                  if (Date.now() - this.sceneStartTime > this.testState)
-                  {
-                     this.testState+=100;
-                     this.add3DObject(this.k3d.objects.length);
-                  }
-                  break;
-               }
-               case 2:
-               {
-                  if (Date.now() - this.sceneStartTime > this.testState)
-                  {
-                     this.testState+=2;
-                  }
-                  break;
-               }
-            }
-         }
-      },
-      
-      /**
-       * Scene rendering event handler
-       */
-      onRenderScene: function onRenderScene(ctx)
-      {
-         ctx.clearRect(0, 0, GameHandler.width, GameHandler.height);
-         
-         // render feature benchmark
-         var width = GameHandler.width, height = GameHandler.height;
-         switch (this.feature)
-         {
-            case 0:
-            {
-               var dist = function dist(a, b, c, d)
-               {
-                  return Math.sqrt((a - c) * (a - c) + (b - d) * (b - d));
-               }
-               
-               // plasma source width and height - variable benchmark state
-               var pwidth = this.plasmasize;
-               var pheight = pwidth * (height/width);
-               // scale the plasma source to the canvas width/height
-               var vpx = width/pwidth, vpy = height/pheight;
-               var time = Date.now() / 64;
-               
-               var colour = function colour(x, y)
-               {
-                  // plasma function
-                  return (128 + (128 * Math.sin(x * 0.0625)) +
-                          128 + (128 * Math.sin(y * 0.03125)) +
-                          128 + (128 * Math.sin(dist(x + time, y - time, width, height) * 0.125)) +
-                          128 + (128 * Math.sin(Math.sqrt(x * x + y * y) * 0.125)) ) * 0.25;
-               }
-               
-               // render plasma effect
-               for (var y=0,x; y<pheight; y++)
-               {
-                  for (x=0; x<pwidth; x++)
-                  {
-                     // map plasma pixels to canvas pixels using the virtual pixel size
-                     ctx.fillStyle = this.palette[~~(colour(x, y) + this.paletteoffset) % 256];
-                     ctx.fillRect(Math.floor(x * vpx), Math.floor(y * vpy), Math.ceil(vpx), Math.ceil(vpy));
-                  }
-               }
-               
-               // palette cycle speed
-               this.paletteoffset++;
-               break;
-            }
-            
-            case 1:
-            {
-               this.k3d.render(ctx);
-               break;
-            }
-
-            case 2:
-            {
-               //
-               // TODO: add more interesting image!
-               //
-               var s = this.testState < GameHandler.width ? this.testState : GameHandler.width;
-               ctx.drawImage(Feature.blurImage, 0, 0, GameHandler.width, GameHandler.height);
-               boxBlurCanvasRGBA( ctx, 0, 0, s, s, s >> 4 + 1, 1 );
-               break;
-            }
-         }
-         
-         ctx.save();
-         ctx.shadowBlur = 0;
-         // Benchmark - information output
-         if (this.sceneCompletedTime)
-         {
-            Game.fillText(ctx, "TEST "+this.test+" COMPLETED: "+this.getTransientTestScore(), "20pt Courier New", 4, 40, "white");
-         }
-         Game.fillText(ctx, "SCORE: " + this.getTransientTestScore(), "12pt Courier New", 0, GameHandler.height - 42, "lightblue");
-         Game.fillText(ctx, "TSF: " + Math.round(GameHandler.frametime) + "ms", "12pt Courier New", 0, GameHandler.height - 22, "lightblue");
-         Game.fillText(ctx, "FPS: " + GameHandler.lastfps, "12pt Courier New", 0, GameHandler.height - 2, "lightblue");
-         ctx.restore();
-      }
-   });
-})();
-
-/*
-Superfast Blur - a fast Box Blur For Canvas
-
-Version:    0.5
-Author:     Mario Klingemann
-Contact:    mario@quasimondo.com
-Website: http://www.quasimondo.com/BoxBlurForCanvas
-Twitter: @quasimondo
-
-In case you find this class useful - especially in commercial projects -
-I am not totally unhappy for a small donation to my PayPal account
-mario@quasimondo.de
-
-Or support me on flattr:
-https://flattr.com/thing/140066/Superfast-Blur-a-pretty-fast-Box-Blur-Effect-for-CanvasJavascript
-
-Copyright (c) 2011 Mario Klingemann
-
-Permission is hereby granted, free of charge, to any person
-obtaining a copy of this software and associated documentation
-files (the "Software"), to deal in the Software without
-restriction, including without limitation the rights to use,
-copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the
-Software is furnished to do so, subject to the following
-conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-OTHER DEALINGS IN THE SOFTWARE.
-*/
-var mul_table = [ 1,57,41,21,203,34,97,73,227,91,149,62,105,45,39,137,241,107,3,173,39,71,65,238,219,101,187,87,81,151,141,133,249,117,221,209,197,187,177,169,5,153,73,139,133,127,243,233,223,107,103,99,191,23,177,171,165,159,77,149,9,139,135,131,253,245,119,231,224,109,211,103,25,195,189,23,45,175,171,83,81,79,155,151,147,9,141,137,67,131,129,251,123,30,235,115,113,221,217,53,13,51,50,49,193,189,185,91,179,175,43,169,83,163,5,79,155,19,75,147,145,143,35,69,17,67,33,65,255,251,247,243,239,59,29,229,113,111,219,27,213,105,207,51,201,199,49,193,191,47,93,183,181,179,11,87,43,85,167,165,163,161,159,157,155,77,19,75,37,73,145,143,141,35,138,137,135,67,33,131,129,255,63,250,247,61,121,239,237,117,29,229,227,225,111,55,109,216,213,211,209,207,205,203,201,199,197,195,193,48,190,47,93,185,183,181,179,178,176,175,173,171,85,21,167,165,41,163,161,5,79,157,78,154,153,19,75,149,74,147,73,144,143,71,141,140,139,137,17,135,134,133,66,131,65,129,1];
-
-var shg_table = [0,9,10,10,14,12,14,14,16,15,16,15,16,15,15,17,18,17,12,18,16,17,17,19,19,18,19,18,18,19,19,19,20,19,20,20,20,20,20,20,15,20,19,20,20,20,21,21,21,20,20,20,21,18,21,21,21,21,20,21,17,21,21,21,22,22,21,22,22,21,22,21,19,22,22,19,20,22,22,21,21,21,22,22,22,18,22,22,21,22,22,23,22,20,23,22,22,23,23,21,19,21,21,21,23,23,23,22,23,23,21,23,22,23,18,22,23,20,22,23,23,23,21,22,20,22,21,22,24,24,24,24,24,22,21,24,23,23,24,21,24,23,24,22,24,24,22,24,24,22,23,24,24,24,20,23,22,23,24,24,24,24,24,24,24,23,21,23,22,23,24,24,24,22,24,24,24,23,22,24,24,25,23,25,25,23,24,25,25,24,22,25,25,25,24,23,24,25,25,25,25,25,25,25,25,25,25,25,25,23,25,23,24,25,25,25,25,25,25,25,25,25,24,22,25,25,23,25,25,20,24,25,24,25,25,22,24,25,24,25,24,25,25,24,25,25,25,25,22,25,25,25,24,25,24,25,18];
-
-function boxBlurCanvasRGBA( context, top_x, top_y, width, height, radius, iterations ){
-   radius |= 0;
-   
-   var imageData = context.getImageData( top_x, top_y, width, height );
-   var pixels = imageData.data;
-   
-   var rsum,gsum,bsum,asum,x,y,i,p,p1,p2,yp,yi,yw,idx,pa;      
-   var wm = width - 1;
-   var hm = height - 1;
-   var wh = width * height;
-   var rad1 = radius + 1;
-    
-   var mul_sum = mul_table[radius];
-   var shg_sum = shg_table[radius];
-
-   var r = [];
-   var g = [];
-   var b = [];
-   var a = [];
-   
-   var vmin = [];
-   var vmax = [];
-  
-   while ( iterations-- > 0 ){
-      yw = yi = 0;
-    
-      for ( y=0; y < height; y++ ){
-         rsum = pixels[yw]   * rad1;
-         gsum = pixels[yw+1] * rad1;
-         bsum = pixels[yw+2] * rad1;
-         asum = pixels[yw+3] * rad1;
-         
-         for( i = 1; i <= radius; i++ ){
-            p = yw + (((i > wm ? wm : i )) << 2 );
-            rsum += pixels[p++];
-            gsum += pixels[p++];
-            bsum += pixels[p++];
-            asum += pixels[p]
-         }
-         
-         for ( x = 0; x < width; x++ ) {
-            r[yi] = rsum;
-            g[yi] = gsum;
-            b[yi] = bsum;
-            a[yi] = asum;
-
-            if( y==0) {
-               vmin[x] = ( ( p = x + rad1) < wm ? p : wm ) << 2;
-               vmax[x] = ( ( p = x - radius) > 0 ? p << 2 : 0 );
-            } 
-            
-            p1 = yw + vmin[x];
-            p2 = yw + vmax[x];
-              
-            rsum += pixels[p1++] - pixels[p2++];
-            gsum += pixels[p1++] - pixels[p2++];
-            bsum += pixels[p1++] - pixels[p2++];
-            asum += pixels[p1]   - pixels[p2];
-                
-            yi++;
-         }
-         yw += ( width << 2 );
-      }
-     
-      for ( x = 0; x < width; x++ ) {
-         yp = x;
-         rsum = r[yp] * rad1;
-         gsum = g[yp] * rad1;
-         bsum = b[yp] * rad1;
-         asum = a[yp] * rad1;
-         
-         for( i = 1; i <= radius; i++ ) {
-           yp += ( i > hm ? 0 : width );
-           rsum += r[yp];
-           gsum += g[yp];
-           bsum += b[yp];
-           asum += a[yp];
-         }
-         
-         yi = x << 2;
-         for ( y = 0; y < height; y++) {
-            
-            pixels[yi+3] = pa = (asum * mul_sum) >>> shg_sum;
-            if ( pa > 0 )
-            {
-               pa = 255 / pa;
-               pixels[yi]   = ((rsum * mul_sum) >>> shg_sum) * pa;
-               pixels[yi+1] = ((gsum * mul_sum) >>> shg_sum) * pa;
-               pixels[yi+2] = ((bsum * mul_sum) >>> shg_sum) * pa;
-            } else {
-               pixels[yi] = pixels[yi+1] = pixels[yi+2] = 0;
-            }           
-            if( x == 0 ) {
-               vmin[y] = ( ( p = y + rad1) < hm ? p : hm ) * width;
-               vmax[y] = ( ( p = y - radius) > 0 ? p * width : 0 );
-            } 
-           
-            p1 = x + vmin[y];
-            p2 = x + vmax[y];
-
-            rsum += r[p1] - r[p2];
-            gsum += g[p1] - g[p2];
-            bsum += b[p1] - b[p2];
-            asum += a[p1] - a[p2];
-
-            yi += width << 2;
-         }
-      }
-   }
-   
-   context.putImageData( imageData, top_x, top_y );
-}/**
- * Asteroids HTML5 Canvas Game
- * Scenes for CanvasMark Rendering Benchmark - March 2013 
- *
- * @email kevtoast at yahoo dot com
- * @twitter kevinroast
- *
- * (C) 2013 Kevin Roast
- * 
- * Please see: license.txt
- * You are welcome to use this code, but I would appreciate an email or tweet
- * if you do anything interesting with it!
- */
-
-
-// Globals
-var BITMAPS = true;
-var GLOWEFFECT = false;
-var g_asteroidImg1 = new Image();
-var g_asteroidImg2 = new Image();
-var g_asteroidImg3 = new Image();
-var g_asteroidImg4 = new Image();
-var g_shieldImg = new Image();
-var g_backgroundImg = new Image();
-var g_playerImg = new Image();
-var g_enemyshipImg = new Image();
-
-
-/**
- * Asteroids root namespace.
- * 
- * @namespace Asteroids
- */
-if (typeof Asteroids == "undefined" || !Asteroids)
-{
-   var Asteroids = {};
-}
-
-
-/**
- * Asteroids benchmark test class.
- * 
- * @namespace Asteroids
- * @class Asteroids.Test
- */
-(function()
-{
-   Asteroids.Test = function(benchmark, loader)
-   {
-      // get the image graphics loading
-      loader.addImage(g_backgroundImg, './images/bg3_1.jpg');
-      loader.addImage(g_playerImg, './images/player.png');
-      loader.addImage(g_asteroidImg1, './images/asteroid1.png');
-      loader.addImage(g_asteroidImg2, './images/asteroid2.png');
-      loader.addImage(g_asteroidImg3, './images/asteroid3.png');
-      loader.addImage(g_asteroidImg4, './images/asteroid4.png');
-      loader.addImage(g_enemyshipImg, './images/enemyship1.png');
-      
-      // generate the single player actor - available across all scenes
-      this.player = new Asteroids.Player(new Vector(GameHandler.width / 2, GameHandler.height / 2), new Vector(0.0, 0.0), 0.0);
-      
-      // add the Asteroid game benchmark scenes
-      for (var level, i=0, t=benchmark.scenes.length; i<4; i++)
-      {
-         level = new Asteroids.BenchMarkScene(this, t+i, i+1);// NOTE: asteroids indexes feature from 1...
-         benchmark.addBenchmarkScene(level);
-      }
-   };
-   
-   Asteroids.Test.prototype =
-   {
-      /**
-       * Reference to the single game player actor
-       */
-      player: null,
-      
-      /**
-       * Lives count (only used to render overlay graphics during benchmark mode)
-       */
-      lives: 3
-   };
-})();
-
-
-/**
- * Asteroids Benchmark scene class.
- * 
- * @namespace Asteroids
- * @class Asteroids.BenchMarkScene
- */
-(function()
-{
-   Asteroids.BenchMarkScene = function(game, test, feature)
-   {
-      this.game = game;
-      this.test = test;
-      this.feature = feature;
-      this.player = game.player;
-      
-      var msg = "Test " + test + " - Asteroids - ";
-      switch (feature)
-      {
-         case 1: msg += "Bitmaps"; break;
-         case 2: msg += "Vectors"; break;
-         case 3: msg += "Bitmaps, shapes, text"; break;
-         case 4: msg += "Shapes, shadows, blending"; break;
-      }
-      var interval = new Game.Interval(msg, this.intervalRenderer);
-      Asteroids.BenchMarkScene.superclass.constructor.call(this, true, interval);
-      
-      // generate background starfield
-      for (var star, i=0; i<this.STARFIELD_SIZE; i++)
-      {
-         star = new Asteroids.Star();
-         star.init();
-         this.starfield.push(star);
-      }
-   };
-   
-   extend(Asteroids.BenchMarkScene, Game.Scene,
-   {
-      STARFIELD_SIZE: 32,
-      
-      game: null,
-      
-      test: 0,
-      
-      /**
-       * Local reference to the game player actor
-       */
-      player: null,
-      
-      /**
-       * Top-level list of game actors sub-lists
-       */
-      actors: null,
-      
-      /**
-       * List of player fired bullet actors
-       */
-      playerBullets: null,
-      
-      /**
-       * List of enemy actors (asteroids, ships etc.)
-       */
-      enemies: null,
-      
-      /**
-       * List of enemy fired bullet actors
-       */
-      enemyBullets: null,
-      
-      /**
-       * List of effect actors
-       */
-      effects: null,
-      
-      /**
-       * Background scrolling bitmap x position
-       */
-      backgroundX: 0,
-      
-      /**
-       * Background starfield star list
-       */
-      starfield: [],
-      
-      /**
-       * Update each individual star in the starfield background
-       */
-      updateStarfield: function updateStarfield(ctx)
-      {
-         for (var s, i=0, j=this.starfield.length; i<j; i++)
-         {
-            s = this.starfield[i];
-            s.render(ctx);
-            s.z -= s.VELOCITY * 0.1;
-            if (s.z < 0.1 || s.prevx > GameHandler.height || s.prevy > GameHandler.width)
-            {
-               s.init();
-            }
-         }
-      },
-      
-      /**
-       * Scene init event handler
-       */
-      onInitScene: function onInitScene()
-      {
-         // generate the actors and add the actor sub-lists to the main actor list
-         this.actors = [];
-         this.enemies = [];
-         this.actors.push(this.enemies);
-         this.actors.push(this.playerBullets = []);
-         this.actors.push(this.enemyBullets = []);
-         this.actors.push(this.effects = []);
-         
-         this.actors.push([this.player]);
-         
-         // reset the player position
-         with (this.player)
-         {
-            position.x = GameHandler.width / 2;
-            position.y = GameHandler.height / 2;
-            vector.x = 0.0;
-            vector.y = 0.0;
-            heading = 0.0;
-         }
-         
-         // tests 1-2 display asteroids in various modes
-         switch (this.feature)
-         {
-            case 1:
-            {
-               // start with 10 asteroids - more will be added if framerate is acceptable
-               for (var i=0; i<10; i++)
-               {
-                  this.enemies.push(this.generateAsteroid(Math.random()+1.0, ~~(Math.random()*4) + 1));
-               }
-               this.testScore = 10;
-               break;
-            }
-            case 2:
-            {
-               // start with 10 asteroids - more will be added if framerate is acceptable
-               for (var i=0; i<10; i++)
-               {
-                  this.enemies.push(this.generateAsteroid(Math.random()+1.0, ~~(Math.random()*4) + 1));
-               }
-               this.testScore = 20;
-               break;
-            }
-            case 3:
-            {
-               // test 3 generates lots of enemy ships that fire
-               for (var i=0; i<10; i++)
-               {
-                  this.enemies.push(new Asteroids.EnemyShip(this, i%2));
-               }
-               this.testScore = 10;
-               break;
-            }
-            case 4:
-            {
-               this.testScore = 25;
-               break;
-            }
-         }
-         
-         // tests 2 in wireframe, all others are bitmaps
-         BITMAPS = !(this.feature === 2);
-         
-         // reset interval flag
-         this.interval.reset();
-      },
-      
-      /**
-       * Scene before rendering event handler
-       */
-      onBeforeRenderScene: function onBeforeRenderScene(benchmark)
-      {
-         // add items to the test
-         if (benchmark)
-         {
-            switch (this.feature)
-            {
-               case 1:
-               case 2:
-               {
-                  var count;
-                  switch (this.feature)
-                  {
-                     case 1:
-                        count = 10;
-                        break;
-                     case 2:
-                        count = 5;
-                        break;
-                  }
-                  for (var i=0; i<count; i++)
-                  {
-                     this.enemies.push(this.generateAsteroid(Math.random()+1.0, ~~(Math.random()*4) + 1));
-                  }
-                  break;
-               }
-               case 3:
-               {
-                  if (Date.now() - this.sceneStartTime > this.testState)
-                  {
-                     this.testState += 20;
-                     for (var i=0; i<2; i++)
-                     {
-                        this.enemies.push(new Asteroids.EnemyShip(this, i%2));
-                     }
-                     this.enemies[0].hit();
-                     this.destroyEnemy(this.enemies[0], new Vector(0, 1));
-                  }
-                  break;
-               }
-               case 4:
-               {
-                  if (Date.now() - this.sceneStartTime > this.testState)
-                  {
-                     this.testState += 25;
-                     
-                     // spray forward guns
-                     for (var i=0; i<=~~(this.testState/500); i++)
-                     {
-                        h = this.player.heading - 15;
-                        t = new Vector(0.0, -7.0).rotate(h * RAD).add(this.player.vector);
-                        this.playerBullets.push(new Asteroids.Bullet(this.player.position.clone(), t, h));
-                        h = this.player.heading;
-                        t = new Vector(0.0, -7.0).rotate(h * RAD).add(this.player.vector);
-                        this.playerBullets.push(new Asteroids.Bullet(this.player.position.clone(), t, h));
-                        h = this.player.heading + 15;
-                        t = new Vector(0.0, -7.0).rotate(h * RAD).add(this.player.vector);
-                        this.playerBullets.push(new Asteroids.Bullet(this.player.position.clone(), t, h));
-                     }
-                     
-                     // side firing guns also
-                     h = this.player.heading - 90;
-                     t = new Vector(0.0, -8.0).rotate(h * RAD).add(this.player.vector);
-                     this.playerBullets.push(new Asteroids.Bullet(this.player.position.clone(), t, h, 25));
-                     
-                     h = this.player.heading + 90;
-                     t = new Vector(0.0, -8.0).rotate(h * RAD).add(this.player.vector);
-                     this.playerBullets.push(new Asteroids.Bullet(this.player.position.clone(), t, h, 25));
-                     
-                     // update player heading to rotate
-                     this.player.heading += 8;
-                  }
-                  break;
-               }
-            }
-         }
-         
-         // update all actors using their current vector
-         this.updateActors();
-      },
-      
-      /**
-       * Scene rendering event handler
-       */
-      onRenderScene: function onRenderScene(ctx)
-      {
-         // setup canvas for a render pass and apply background
-         if (BITMAPS)
-         {
-            // draw a scrolling background image
-            ctx.drawImage(g_backgroundImg, this.backgroundX++, 0, GameHandler.width, GameHandler.height, 0, 0, GameHandler.width, GameHandler.height);
-            if (this.backgroundX == (g_backgroundImg.width / 2))
-            {
-               this.backgroundX = 0;
-            }
-            ctx.shadowBlur = 0;
-         }
-         else
-         {
-            // clear the background to black
-            ctx.fillStyle = "black";
-            ctx.fillRect(0, 0, GameHandler.width, GameHandler.height);
-            
-            // glowing vector effect shadow
-            ctx.shadowBlur = GLOWEFFECT ? 8 : 0;
-            
-            // update and render background starfield effect
-            this.updateStarfield(ctx);
-         }
-         
-         // render the game actors
-         this.renderActors(ctx);
-         
-         // render info overlay graphics
-         this.renderOverlay(ctx);
-      },
-      
-      /**
-       * Randomly generate a new large asteroid. Ensures the asteroid is not generated
-       * too close to the player position!
-       * 
-       * @param speedFactor {number} Speed multiplier factor to apply to asteroid vector
-       */
-      generateAsteroid: function generateAsteroid(speedFactor, size)
-      {
-         while (true)
-         {
-            // perform a test to check it is not too close to the player
-            var apos = new Vector(Math.random()*GameHandler.width, Math.random()*GameHandler.height);
-            if (this.player.position.distance(apos) > 125)
-            {
-               var vec = new Vector( ((Math.random()*2)-1)*speedFactor, ((Math.random()*2)-1)*speedFactor );
-               var asteroid = new Asteroids.Asteroid(
-                  apos, vec, size ? size : 4);
-               return asteroid;
-            }
-         }
-      },
-      
-      /**
-       * Update the actors position based on current vectors and expiration.
-       */
-      updateActors: function updateActors()
-      {
-         for (var i = 0, j = this.actors.length; i < j; i++)
-         {
-            var actorList = this.actors[i];
-            
-            for (var n = 0; n < actorList.length; n++)
-            {
-               var actor = actorList[n];
-               
-               // call onUpdate() event for each actor
-               actor.onUpdate(this);
-               
-               // expiration test first
-               if (actor.expired())
-               {
-                  actorList.splice(n, 1);
-               }
-               else
-               {
-                  // update actor using its current vector
-                  actor.position.add(actor.vector);
-                  
-                  // handle traversing out of the coordinate space and back again
-                  if (actor.position.x >= GameHandler.width)
-                  {
-                     actor.position.x = 0;
-                  }
-                  else if (actor.position.x < 0)
-                  {
-                     actor.position.x = GameHandler.width - 1;
-                  }
-                  if (actor.position.y >= GameHandler.height)
-                  {
-                     actor.position.y = 0;
-                  }
-                  else if (actor.position.y < 0)
-                  {
-                     actor.position.y = GameHandler.height - 1;
-                  }
-               }
-            }
-         }
-      },
-      
-      /**
-       * Blow up an enemy.
-       * 
-       * An asteroid may generate new baby asteroids and leave an explosion
-       * in the wake.
-       * 
-       * Also applies the score for the destroyed item.
-       * 
-       * @param enemy {Game.Actor} The enemy to destory and add score for
-       * @param parentVector {Vector} The vector of the item that hit the enemy
-       * @param player {boolean} If true, the player was the destroyed
-       */
-      destroyEnemy: function destroyEnemy(enemy, parentVector)
-      {
-         if (enemy instanceof Asteroids.Asteroid)
-         {
-            // generate baby asteroids
-            this.generateBabyAsteroids(enemy, parentVector);
-            
-            // add an explosion actor at the asteriod position and vector
-            var boom = new Asteroids.Explosion(enemy.position.clone(), enemy.vector.clone(), enemy.size);
-            this.effects.push(boom);
-            
-            // generate a score effect indicator at the destroyed enemy position
-            var vec = new Vector(0, -(Math.random()*2 + 0.5));
-            var effect = new Asteroids.ScoreIndicator(
-                  new Vector(enemy.position.x, enemy.position.y), vec, Math.floor(100 + (Math.random()*100)));
-            this.effects.push(effect);
-         }
-         else if (enemy instanceof Asteroids.EnemyShip)
-         {
-            // add an explosion actor at the asteriod position and vector
-            var boom = new Asteroids.Explosion(enemy.position.clone(), enemy.vector.clone(), 4);
-            this.effects.push(boom);
-            
-            // generate a score text effect indicator at the destroyed enemy position
-            var vec = new Vector(0, -(Math.random()*2 + 0.5));
-            var effect = new Asteroids.ScoreIndicator(
-                  new Vector(enemy.position.x, enemy.position.y), vec, Math.floor(100 + (Math.random()*100)));
-            this.effects.push(effect);
-         }
-      },
-      
-      /**
-       * Generate a number of baby asteroids from a detonated parent asteroid. The number
-       * and size of the generated asteroids are based on the parent size. Some of the
-       * momentum of the parent vector (e.g. impacting bullet) is applied to the new asteroids.
-       *
-       * @param asteroid {Asteroids.Asteroid} The parent asteroid that has been destroyed
-       * @param parentVector {Vector} Vector of the impacting object e.g. a bullet
-       */
-      generateBabyAsteroids: function generateBabyAsteroids(asteroid, parentVector)
-      {
-         // generate some baby asteroid(s) if bigger than the minimum size
-         if (asteroid.size > 1)
-         {
-            for (var x=0, xc=Math.floor(asteroid.size/2); x<xc; x++)
-            {
-               var babySize = asteroid.size - 1;
-               
-               var vec = asteroid.vector.clone();
-               
-               // apply a small random vector in the direction of travel
-               var t = new Vector(0.0, -(Math.random() * 3));
-               
-               // rotate vector by asteroid current heading - slightly randomized
-               t.rotate(asteroid.vector.theta() * (Math.random()*Math.PI));
-               vec.add(t);
-               
-               // add the scaled parent vector - to give some momentum from the impact
-               vec.add(parentVector.clone().scale(0.2));
-               
-               // create the asteroid - slightly offset from the centre of the old one
-               var baby = new Asteroids.Asteroid(
-                     new Vector(asteroid.position.x + (Math.random()*5)-2.5, asteroid.position.y + (Math.random()*5)-2.5),
-                     vec, babySize, asteroid.type);
-               this.enemies.push(baby);
-            }
-         }
-      },
-      
-      /**
-       * Render each actor to the canvas.
-       * 
-       * @param ctx {object} Canvas rendering context
-       */
-      renderActors: function renderActors(ctx)
-      {
-         for (var i = 0, j = this.actors.length; i < j; i++)
-         {
-            // walk each sub-list and call render on each object
-            var actorList = this.actors[i];
-            
-            for (var n = actorList.length - 1; n >= 0; n--)
-            {
-               actorList[n].onRender(ctx);
-            }
-         }
-      },
-      
-      /**
-       * Render player information HUD overlay graphics.
-       * 
-       * @param ctx {object} Canvas rendering context
-       */
-      renderOverlay: function renderOverlay(ctx)
-      {
-         ctx.save();
-         
-         // energy bar (100 pixels across, scaled down from player energy max)
-         ctx.strokeStyle = "rgb(50,50,255)";
-         ctx.strokeRect(4, 4, 101, 6);
-         ctx.fillStyle = "rgb(100,100,255)";
-         var energy = this.player.energy;
-         if (energy > this.player.ENERGY_INIT)
-         {
-            // the shield is on for "free" briefly when he player respawns
-            energy = this.player.ENERGY_INIT;
-         }
-         ctx.fillRect(5, 5, (energy / (this.player.ENERGY_INIT / 100)), 5);
-         
-         // lives indicator graphics
-         for (var i=0; i<this.game.lives; i++)
-         {
-            if (BITMAPS)
-            {
-               ctx.drawImage(g_playerImg, 0, 0, 64, 64, 350+(i*20), 0, 16, 16);
-            }
-            else
-            {
-               ctx.save();
-               ctx.shadowColor = ctx.strokeStyle = "rgb(255,255,255)";
-               ctx.translate(360+(i*16), 8);
-               ctx.beginPath();
-               ctx.moveTo(-4, 6);
-               ctx.lineTo(4, 6);
-               ctx.lineTo(0, -6);
-               ctx.closePath();
-               ctx.stroke();
-               ctx.restore();
-            }
-         }
-         
-         // score display
-         Game.fillText(ctx, "00000000", "12pt Courier New", 120, 12, "white");
-         
-         // high score
-         Game.fillText(ctx, "HI: 00000000", "12pt Courier New", 220, 12, "white");
-         
-         // Benchmark - information output
-         if (this.sceneCompletedTime)
-         {
-            Game.fillText(ctx, "TEST " + this.feature + " COMPLETED: " + this.getTransientTestScore(), "20pt Courier New", 4, 40, "white");
-         }
-         Game.fillText(ctx, "SCORE: " + this.getTransientTestScore(), "12pt Courier New", 0, GameHandler.height - 42, "lightblue");
-         Game.fillText(ctx, "TSF: " + Math.round(GameHandler.frametime) + "ms", "12pt Courier New", 0, GameHandler.height - 22, "lightblue");
-         Game.fillText(ctx, "FPS: " + GameHandler.lastfps, "12pt Courier New", 0, GameHandler.height - 2, "lightblue");
-         
-         ctx.restore();
-      }
-   });
-})();
-
-
-/**
- * Starfield star class.
- * 
- * @namespace Asteroids
- * @class Asteroids.Star
- */
-(function()
-{
-   Asteroids.Star = function()
-   {
-      return this;
-   };
-   
-   Asteroids.Star.prototype =
-   {
-      MAXZ: 12.0,
-      VELOCITY: 1.5,
-      MAXSIZE: 5,
-      
-      x: 0,
-      y: 0,
-      z: 0,
-      prevx: 0,
-      prevy: 0,
-      
-      init: function init()
-      {
-         // select a random point for the initial location
-         this.x = (Math.random() * GameHandler.width - (GameHandler.width * 0.5)) * this.MAXZ;
-         this.y = (Math.random() * GameHandler.height - (GameHandler.height * 0.5)) * this.MAXZ;
-         this.z = this.MAXZ;
-      },
-      
-      render: function render(ctx)
-      {
-         var xx = this.x / this.z;
-         var yy = this.y / this.z;
-         
-         var size = 1.0 / this.z * this.MAXSIZE + 1;
-         
-         ctx.save();
-         ctx.fillStyle = "rgb(200,200,200)";
-         ctx.beginPath();
-         ctx.arc(xx + (GameHandler.width / 2), yy +(GameHandler.height / 2), size/2, 0, TWOPI, true);
-         ctx.closePath();
-         ctx.fill();
-         ctx.restore();
-         
-         this.prevx = xx;
-         this.prevy = yy;
-      }
-   };
-})();
-/**
- * Player actor class.
- *
- * @namespace Asteroids
- * @class Asteroids.Player
- */
-(function()
-{
-   Asteroids.Player = function(p, v, h)
-   {
-      Asteroids.Player.superclass.constructor.call(this, p, v);
-      this.heading = h;
-      this.energy = this.ENERGY_INIT;
-      
-      // setup SpriteActor values - used for shield sprite
-      this.animImage = g_shieldImg;
-      this.animLength = this.SHIELD_ANIM_LENGTH;
-      
-      // setup weapons
-      this.primaryWeapons = [];
-      this.primaryWeapons["main"] = new Asteroids.PrimaryWeapon(this);
-      
-      return this;
-   };
-   
-   extend(Asteroids.Player, Game.SpriteActor,
-   {
-      MAX_PLAYER_VELOCITY: 10.0,
-      PLAYER_RADIUS: 10,
-      SHIELD_RADIUS: 14,
-      SHIELD_ANIM_LENGTH: 100,
-      SHIELD_MIN_PULSE: 20,
-      ENERGY_INIT: 200,
-      THRUST_DELAY: 1,
-      BOMB_RECHARGE: 20,
-      BOMB_ENERGY: 40,
-      
-      /**
-       * Player heading
-       */
-      heading: 0.0,
-      
-      /**
-       * Player energy (shield and bombs)
-       */
-      energy: 0,
-      
-      /**
-       * Player shield active counter
-       */
-      shieldCounter: 0,
-      
-      /**
-       * Player 'alive' flag
-       */
-      alive: true,
-      
-      /**
-       * Primary weapon list
-       */
-      primaryWeapons: null,
-      
-      /**
-       * Bomb fire recharging counter
-       */
-      bombRecharge: 0,
-      
-      /**
-       * Engine thrust recharge counter
-       */
-      thrustRecharge: 0,
-      
-      /**
-       * True if the engine thrust graphics should be rendered next frame
-       */
-      engineThrust: false,
-      
-      /**
-       * Frame that the player was killed on - to cause a delay before respawning the player
-       */
-      killedOnFrame: 0,
-      
-      /**
-       * Power up setting - can fire when shielded
-       */
-      fireWhenShield: false,
-      
-      /**
-       * Player rendering method
-       * 
-       * @param ctx {object} Canvas rendering context
-       */
-      onRender: function onRender(ctx)
-      {
-         var headingRad = this.heading * RAD;
-         
-         // render engine thrust?
-         if (this.engineThrust)
-         {
-            ctx.save();
-            ctx.translate(this.position.x, this.position.y);
-            ctx.rotate(headingRad);
-            ctx.globalAlpha = 0.4 + Math.random()/2;
-            if (BITMAPS)
-            {
-               ctx.fillStyle = "rgb(25,125,255)";
-            }
-            else
-            {
-               ctx.shadowColor = ctx.strokeStyle = "rgb(25,125,255)";
-            }
-            ctx.beginPath();
-            ctx.moveTo(-5, 8);
-            ctx.lineTo(5, 8);
-            ctx.lineTo(0, 15 + Math.random()*7);
-            ctx.closePath();
-            if (BITMAPS) ctx.fill(); else ctx.stroke();
-            ctx.restore();
-            this.engineThrust = false;
-         }
-         
-         // render player graphic
-         if (BITMAPS)
-         {
-            var size = (this.PLAYER_RADIUS * 2) + 4;
-            var normAngle = this.heading % 360;
-            if (normAngle < 0)
-            {
-               normAngle = 360 + normAngle;
-            }
-            ctx.drawImage(g_playerImg, 0, normAngle * 16, 64, 64, this.position.x - (size / 2), this.position.y - (size / 2), size, size);
-         }
-         else
-         {
-            ctx.save();
-            ctx.shadowColor = ctx.strokeStyle = "rgb(255,255,255)";
-            ctx.translate(this.position.x, this.position.y);
-            ctx.rotate(headingRad);
-            ctx.beginPath();
-            ctx.moveTo(-6, 8);
-            ctx.lineTo(6, 8);
-            ctx.lineTo(0, -8);
-            ctx.closePath();
-            ctx.stroke();
-            ctx.restore();
-         }
-         
-         // shield up? if so render a shield graphic around the ship
-         if (this.shieldCounter > 0 && this.energy > 0)
-         {
-            if (BITMAPS)
-            {
-               // render shield graphic bitmap
-               ctx.save();
-               ctx.translate(this.position.x, this.position.y);
-               ctx.rotate(headingRad);
-               this.renderSprite(ctx, -this.SHIELD_RADIUS-1, -this.SHIELD_RADIUS-1, (this.SHIELD_RADIUS * 2) + 2);
-               ctx.restore();
-            }
-            else
-            {
-               // render shield as a simple circle around the ship
-               ctx.save();
-               ctx.translate(this.position.x, this.position.y);
-               ctx.rotate(headingRad);
-               ctx.shadowColor = ctx.strokeStyle = "rgb(100,100,255)";
-               ctx.beginPath();
-               ctx.arc(0, 2, this.SHIELD_RADIUS, 0, TWOPI, true);
-               ctx.closePath();
-               ctx.stroke();
-               ctx.restore();
-            }
-            
-            this.shieldCounter--;
-            this.energy--;
-         }
-      },
-      
-      /**
-       * Execute player forward thrust request
-       * Automatically a delay is used between each application - to ensure stable thrust on all machines.
-       */
-      thrust: function thrust()
-      {
-         // now test we did not thrust too recently - to stop fast key repeat issues
-         if (GameHandler.frameCount - this.thrustRecharge > this.THRUST_DELAY)
-         {
-            // update last frame count
-            this.thrustRecharge = GameHandler.frameCount;
-            
-            // generate a small thrust vector
-            var t = new Vector(0.0, -0.55);
-            
-            // rotate thrust vector by player current heading
-            t.rotate(this.heading * RAD);
-            
-            // test player won't then exceed maximum velocity
-            var pv = this.vector.clone();
-            if (pv.add(t).length() < this.MAX_PLAYER_VELOCITY)
-            {
-               // add to current vector (which then gets applied during each render loop)
-               this.vector.add(t);
-            }
-         }
-         // mark so that we know to render engine thrust graphics
-         this.engineThrust = true;
-      },
-      
-      /**
-       * Execute player active shield request
-       * If energy remaining the shield will be briefly applied - or until key is release
-       */
-      activateShield: function activateShield()
-      {
-         // ensure shield stays up for a brief pulse between key presses!
-         if (this.energy > 0)
-         {
-            this.shieldCounter = this.SHIELD_MIN_PULSE;
-         }
-      },
-      
-      isShieldActive: function isShieldActive()
-      {
-         return (this.shieldCounter > 0 && this.energy > 0);
-      },
-      
-      radius: function radius()
-      {
-         return (this.isShieldActive() ? this.SHIELD_RADIUS : this.PLAYER_RADIUS);
-      },
-      
-      expired: function expired()
-      {
-         return !(this.alive);
-      },
-      
-      kill: function kill()
-      {
-         this.alive = false;
-         this.killedOnFrame = GameHandler.frameCount;
-      },
-      
-      /**
-       * Fire primary weapon(s)
-       * @param bulletList {Array} to add bullet(s) to on success
-       */
-      firePrimary: function firePrimary(bulletList)
-      {
-         // attempt to fire the primary weapon(s)
-         // first ensure player is alive and the shield is not up
-         if (this.alive && (!this.isShieldActive() || this.fireWhenShield))
-         {
-            for (var w in this.primaryWeapons)
-            {
-               var b = this.primaryWeapons[w].fire();
-               if (b)
-               {
-                  if (isArray(b))
-                  {
-                     for (var i=0; i<b.length; i++)
-                     {
-                        bulletList.push(b[i]);
-                     }
-                  }
-                  else
-                  {
-                     bulletList.push(b);
-                  }
-               }
-            }
-         }
-      },
-      
-      /**
-       * Fire secondary weapon.
-       * @param bulletList {Array} to add bullet to on success
-       */
-      fireSecondary: function fireSecondary(bulletList)
-      {
-         // attempt to fire the secondary weapon and generate bomb object if successful
-         // first ensure player is alive and the shield is not up
-         if (this.alive && (!this.isShieldActive() || this.fireWhenShield) && this.energy > this.BOMB_ENERGY)
-         {
-            // now test we did not fire too recently
-            if (GameHandler.frameCount - this.bombRecharge > this.BOMB_RECHARGE)
-            {
-               // ok, update last fired frame and we can now generate a bomb
-               this.bombRecharge = GameHandler.frameCount;
-               
-               // decrement energy supply
-               this.energy -= this.BOMB_ENERGY;
-               
-               // generate a vector rotated to the player heading and then add the current player
-               // vector to give the bomb the correct directional momentum
-               var t = new Vector(0.0, -6.0);
-               t.rotate(this.heading * RAD);
-               t.add(this.vector);
-               
-               bulletList.push(new Asteroids.Bomb(this.position.clone(), t));
-            }
-         }
-      },
-      
-      onUpdate: function onUpdate()
-      {
-         // slowly recharge the shield - if not active
-         if (!this.isShieldActive() && this.energy < this.ENERGY_INIT)
-         {
-            this.energy += 0.1;
-         }
-      },
-      
-      reset: function reset(persistPowerUps)
-      {
-         // reset energy, alive status, weapons and power up flags
-         this.alive = true;
-         if (!persistPowerUps)
-         {
-            this.primaryWeapons = [];
-            this.primaryWeapons["main"] = new Asteroids.PrimaryWeapon(this);
-            this.fireWhenShield = false;
-         }
-         this.energy = this.ENERGY_INIT + this.SHIELD_MIN_PULSE;  // for shield as below
-         
-         // active shield briefly
-         this.activateShield();
-      }
-   });
-})();
-/**
- * Asteroid actor class.
- *
- * @namespace Asteroids
- * @class Asteroids.Asteroid
- */
-(function()
-{
-   Asteroids.Asteroid = function(p, v, s, t)
-   {
-      Asteroids.Asteroid.superclass.constructor.call(this, p, v);
-      this.size = s;
-      this.health = s;
-      
-      // randomly select an asteroid image bitmap
-      if (t === undefined)
-      {
-         t = randomInt(1, 4);
-      }
-      eval("this.animImage=g_asteroidImg" + t);
-      this.type = t;
-      
-      // randomly setup animation speed and direction
-      this.animForward = (Math.random() < 0.5);
-      this.animSpeed = 0.25 + Math.random();
-      this.animLength = this.ANIMATION_LENGTH;
-      this.rotation = randomInt(0, 180);
-      this.rotationSpeed = randomInt(-1, 1) / 25;
-      
-      return this;
-   };
-   
-   extend(Asteroids.Asteroid, Game.SpriteActor,
-   {
-      ANIMATION_LENGTH: 180,
-      
-      /**
-       * Asteroid size - values from 1-4 are valid.
-       */
-      size: 0,
-      
-      /**
-       * Asteroid type i.e. which bitmap it is drawn from
-       */
-      type: 1,
-      
-      /**
-       * Asteroid health before it's destroyed
-       */
-      health: 0,
-      
-      /**
-       * Retro graphics mode rotation orientation and speed
-       */
-      rotation: 0,
-      rotationSpeed: 0,
-      
-      /**
-       * Asteroid rendering method
-       */
-      onRender: function onRender(ctx)
-      {
-         var rad = this.size * 8;
-         ctx.save();
-         if (BITMAPS)
-         {
-            // render asteroid graphic bitmap
-            // bitmap is rendered slightly large than the radius as the raytraced asteroid graphics do not
-            // quite touch the edges of the 64x64 sprite - this improves perceived collision detection
-            this.renderSprite(ctx, this.position.x - rad - 2, this.position.y - rad - 2, (rad * 2)+4, true);
-         }
-         else
-         {
-            // draw asteroid outline circle
-            ctx.shadowColor = ctx.strokeStyle = "white";
-            ctx.translate(this.position.x, this.position.y);
-            ctx.scale(this.size * 0.8, this.size * 0.8);
-            ctx.rotate(this.rotation += this.rotationSpeed);
-            ctx.lineWidth = (0.8 / this.size) * 2;
-            ctx.beginPath();
-            // asteroid wires
-            switch (this.type)
-            {
-               case 1:
-                  ctx.moveTo(0,10);
-                  ctx.lineTo(8,6);
-                  ctx.lineTo(10,-4);
-                  ctx.lineTo(4,-2);
-                  ctx.lineTo(6,-6);
-                  ctx.lineTo(0,-10);
-                  ctx.lineTo(-10,-3);
-                  ctx.lineTo(-10,5);
-                  break;
-               case 2:
-                  ctx.moveTo(0,10);
-                  ctx.lineTo(8,6);
-                  ctx.lineTo(10,-4);
-                  ctx.lineTo(4,-2);
-                  ctx.lineTo(6,-6);
-                  ctx.lineTo(0,-10);
-                  ctx.lineTo(-8,-8);
-                  ctx.lineTo(-6,-3);
-                  ctx.lineTo(-8,-4);
-                  ctx.lineTo(-10,5);
-                  break;
-               case 3:
-                  ctx.moveTo(-4,10);
-                  ctx.lineTo(1,8);
-                  ctx.lineTo(7,10);
-                  ctx.lineTo(10,-4);
-                  ctx.lineTo(4,-2);
-                  ctx.lineTo(6,-6);
-                  ctx.lineTo(0,-10);
-                  ctx.lineTo(-10,-3);
-                  ctx.lineTo(-10,5);
-                  break;
-               case 4:
-                  ctx.moveTo(-8,10);
-                  ctx.lineTo(7,8);
-                  ctx.lineTo(10,-2);
-                  ctx.lineTo(6,-10);
-                  ctx.lineTo(-2,-8);
-                  ctx.lineTo(-6,-10);
-                  ctx.lineTo(-10,-6);
-                  ctx.lineTo(-7,0);
-                  break;
-            }
-            ctx.closePath();
-            ctx.stroke();
-         }
-         ctx.restore();
-      },
-      
-      radius: function radius()
-      {
-         return this.size * 8;
-      },
-      
-      /**
-       * Asteroid hit by player bullet
-       * 
-       * @param force of the impacting bullet, -1 for instant kill
-       * @return true if destroyed, false otherwise
-       */
-      hit: function hit(force)
-      {
-         if (force !== -1)
-         {
-            this.health -= force;
-         }
-         else
-         {
-            // instant kill
-            this.health = 0;
-         }
-         return !(this.alive = (this.health > 0));
-      }
-   });
-})();
-
-
-/**
- * Enemy Ship actor class.
- * 
- * @namespace Asteroids
- * @class Asteroids.EnemyShip
- */
-(function()
-{
-   Asteroids.EnemyShip = function(scene, size)
-   {
-      this.size = size;
-      
-      // small ship, alter settings slightly
-      if (this.size === 1)
-      {
-         this.BULLET_RECHARGE = 45;
-         this.RADIUS = 8;
-      }
-      
-      // randomly setup enemy initial position and vector
-      // ensure the enemy starts in the opposite quadrant to the player
-      var p, v;
-      if (scene.player.position.x < GameHandler.width / 2)
-      {
-         // player on left of the screen
-         if (scene.player.position.y < GameHandler.height / 2)
-         {
-            // player in top left of the screen
-            p = new Vector(GameHandler.width-48, GameHandler.height-48);
-         }
-         else
-         {
-            // player in bottom left of the screen
-            p = new Vector(GameHandler.width-48, 48);
-         }
-         v = new Vector(-(Math.random() + 1 + size), Math.random() + 0.5 + size);
-      }
-      else
-      {
-         // player on right of the screen
-         if (scene.player.position.y < GameHandler.height / 2)
-         {
-            // player in top right of the screen
-            p = new Vector(0, GameHandler.height-48);
-         }
-         else
-         {
-            // player in bottom right of the screen
-            p = new Vector(0, 48);
-         }
-         v = new Vector(Math.random() + 1 + size, Math.random() + 0.5 + size);
-      }
-      
-      // setup SpriteActor values
-      this.animImage = g_enemyshipImg;
-      this.animLength = this.SHIP_ANIM_LENGTH;
-      
-      Asteroids.EnemyShip.superclass.constructor.call(this, p, v);
-      
-      return this;
-   };
-   
-   extend(Asteroids.EnemyShip, Game.SpriteActor,
-   {
-      SHIP_ANIM_LENGTH: 90,
-      RADIUS: 16,
-      BULLET_RECHARGE: 60,
-      
-      /**
-       * Enemy ship size - 0 = large (slow), 1 = small (fast)
-       */
-      size: 0,
-      
-      /**
-       * Bullet fire recharging counter
-       */
-      bulletRecharge: 0,
-      
-      onUpdate: function onUpdate(scene)
-      {
-         // change enemy direction randomly
-         if (this.size === 0)
-         {
-            if (Math.random() < 0.01)
-            {
-               this.vector.y = -(this.vector.y + (0.25 - (Math.random()/2)));
-            }
-         }
-         else
-         {
-            if (Math.random() < 0.02)
-            {
-               this.vector.y = -(this.vector.y + (0.5 - Math.random()));
-            }
-         }
-         
-         // regular fire a bullet at the player
-         if (GameHandler.frameCount - this.bulletRecharge > this.BULLET_RECHARGE && scene.player.alive)
-         {
-            // ok, update last fired frame and we can now generate a bullet
-            this.bulletRecharge = GameHandler.frameCount;
-            
-            // generate a vector pointed at the player
-            // by calculating a vector between the player and enemy positions
-            var v = scene.player.position.clone().sub(this.position);
-            // scale resulting vector down to bullet vector size
-            var scale = (this.size === 0 ? 5.0 : 6.0) / v.length();
-            v.x *= scale;
-            v.y *= scale;
-            // slightly randomize the direction (big ship is less accurate also)
-            v.x += (this.size === 0 ? (Math.random() * 2 - 1) : (Math.random() - 0.5));
-            v.y += (this.size === 0 ? (Math.random() * 2 - 1) : (Math.random() - 0.5));
-            // - could add the enemy motion vector for correct momentum
-            // - but problem is this leads to slow bullets firing back from dir of travel
-            // - so pretend that enemies are clever enough to account for this...
-            //v.add(this.vector);
-            
-            var bullet = new Asteroids.EnemyBullet(this.position.clone(), v);
-            scene.enemyBullets.push(bullet);
-         }
-      },
-      
-      /**
-       * Enemy rendering method
-       */
-      onRender: function onRender(ctx)
-      {
-         if (BITMAPS)
-         {
-            // render enemy graphic bitmap
-            var rad = this.RADIUS + 2;
-            this.renderSprite(ctx, this.position.x - rad, this.position.y - rad, rad * 2, true);
-         }
-         else
-         {
-            ctx.save();
-            ctx.translate(this.position.x, this.position.y);
-            if (this.size === 0)
-            {
-               ctx.scale(2, 2);
-            }
-            
-            ctx.beginPath();
-            ctx.moveTo(0, -4);
-            ctx.lineTo(8, 3);
-            ctx.lineTo(0, 8);
-            ctx.lineTo(-8, 3);
-            ctx.lineTo(0, -4);
-            ctx.closePath();
-            ctx.shadowColor = ctx.strokeStyle = "rgb(100,150,100)";
-            ctx.stroke();
-            ctx.beginPath();
-            ctx.moveTo(0, -8);
-            ctx.lineTo(4, -4);
-            ctx.lineTo(0, 0);
-            ctx.lineTo(-4, -4);
-            ctx.lineTo(0, -8);
-            ctx.closePath();
-            ctx.shadowColor = ctx.strokeStyle = "rgb(150,200,150)";
-            ctx.stroke();
-            
-            ctx.restore();
-         }
-      },
-      
-      radius: function radius()
-      {
-         return this.RADIUS;
-      }
-   });
-})();
-/**
- * Weapon system base class for the player actor.
- * 
- * @namespace Asteroids
- * @class Asteroids.Weapon
- */
-(function()
-{
-   Asteroids.Weapon = function(player)
-   {
-      this.player = player;
-      return this;
-   };
-   
-   Asteroids.Weapon.prototype =
-   {
-      WEAPON_RECHARGE: 3,
-      weaponRecharge: 0,
-      player: null,
-      
-      fire: function()
-      {
-         // now test we did not fire too recently
-         if (GameHandler.frameCount - this.weaponRecharge > this.WEAPON_RECHARGE)
-         {
-            // ok, update last fired frame and we can now generate a bullet
-            this.weaponRecharge = GameHandler.frameCount;
-            
-            return this.doFire();
-         }
-      },
-      
-      doFire: function()
-      {
-      }
-   };
-})();
-
-
-/**
- * Basic primary weapon for the player actor.
- * 
- * @namespace Asteroids
- * @class Asteroids.PrimaryWeapon
- */
-(function()
-{
-   Asteroids.PrimaryWeapon = function(player)
-   {
-      Asteroids.PrimaryWeapon.superclass.constructor.call(this, player);
-      return this;
-   };
-   
-   extend(Asteroids.PrimaryWeapon, Asteroids.Weapon,
-   {
-      doFire: function()
-      {
-         // generate a vector rotated to the player heading and then add the current player
-         // vector to give the bullet the correct directional momentum
-         var t = new Vector(0.0, -8.0);
-         t.rotate(this.player.heading * RAD);
-         t.add(this.player.vector);
-         
-         return new Asteroids.Bullet(this.player.position.clone(), t, this.player.heading);
-      }
-   });
-})();
-
-
-/**
- * Twin Cannons primary weapon for the player actor.
- * 
- * @namespace Asteroids
- * @class Asteroids.TwinCannonsWeapon
- */
-(function()
-{
-   Asteroids.TwinCannonsWeapon = function(player)
-   {
-      Asteroids.TwinCannonsWeapon.superclass.constructor.call(this, player);
-      return this;
-   };
-   
-   extend(Asteroids.TwinCannonsWeapon, Asteroids.Weapon,
-   {
-      doFire: function()
-      {
-         var t = new Vector(0.0, -8.0);
-         t.rotate(this.player.heading * RAD);
-         t.add(this.player.vector);
-         
-         return new Asteroids.BulletX2(this.player.position.clone(), t, this.player.heading);
-      }
-   });
-})();
-
-
-/**
- * V Spray Cannons primary weapon for the player actor.
- * 
- * @namespace Asteroids
- * @class Asteroids.VSprayCannonsWeapon
- */
-(function()
-{
-   Asteroids.VSprayCannonsWeapon = function(player)
-   {
-      this.WEAPON_RECHARGE = 5;
-      Asteroids.VSprayCannonsWeapon.superclass.constructor.call(this, player);
-      return this;
-   };
-   
-   extend(Asteroids.VSprayCannonsWeapon, Asteroids.Weapon,
-   {
-      doFire: function()
-      {
-         var t, h;
-         
-         var bullets = [];
-         
-         h = this.player.heading - 15;
-         t = new Vector(0.0, -7.0).rotate(h * RAD).add(this.player.vector);
-         bullets.push(new Asteroids.Bullet(this.player.position.clone(), t, h));
-         
-         h = this.player.heading;
-         t = new Vector(0.0, -7.0).rotate(h * RAD).add(this.player.vector);
-         bullets.push(new Asteroids.Bullet(this.player.position.clone(), t, h));
-         
-         h = this.player.heading + 15;
-         t = new Vector(0.0, -7.0).rotate(h * RAD).add(this.player.vector);
-         bullets.push(new Asteroids.Bullet(this.player.position.clone(), t, h));
-         
-         return bullets;
-      }
-   });
-})();
-
-
-/**
- * Side Guns additional primary weapon for the player actor.
- * 
- * @namespace Asteroids
- * @class Asteroids.SideGunWeapon
- */
-(function()
-{
-   Asteroids.SideGunWeapon = function(player)
-   {
-      this.WEAPON_RECHARGE = 5;
-      Asteroids.SideGunWeapon.superclass.constructor.call(this, player);
-      return this;
-   };
-   
-   extend(Asteroids.SideGunWeapon, Asteroids.Weapon,
-   {
-      doFire: function()
-      {
-         var t, h;
-         
-         var bullets = [];
-         
-         h = this.player.heading - 90;
-         t = new Vector(0.0, -8.0).rotate(h * RAD).add(this.player.vector);
-         bullets.push(new Asteroids.Bullet(this.player.position.clone(), t, h, 25));
-         
-         h = this.player.heading + 90;
-         t = new Vector(0.0, -8.0).rotate(h * RAD).add(this.player.vector);
-         bullets.push(new Asteroids.Bullet(this.player.position.clone(), t, h, 25));
-         
-         return bullets;
-      }
-   });
-})();
-
-
-/**
- * Rear Gun additional primary weapon for the player actor.
- * 
- * @namespace Asteroids
- * @class Asteroids.RearGunWeapon
- */
-(function()
-{
-   Asteroids.RearGunWeapon = function(player)
-   {
-      this.WEAPON_RECHARGE = 5;
-      Asteroids.RearGunWeapon.superclass.constructor.call(this, player);
-      return this;
-   };
-   
-   extend(Asteroids.RearGunWeapon, Asteroids.Weapon,
-   {
-      doFire: function()
-      {
-         var t = new Vector(0.0, -8.0);
-         var h = this.player.heading + 180;
-         t.rotate(h * RAD);
-         t.add(this.player.vector);
-         
-         return new Asteroids.Bullet(this.player.position.clone(), t, h, 25);
-      }
-   });
-})();
-
-
-/**
- * Player Bullet actor class.
- *
- * @namespace Asteroids
- * @class Asteroids.Bullet
- */
-(function()
-{
-   Asteroids.Bullet = function(p, v, h, lifespan)
-   {
-      Asteroids.Bullet.superclass.constructor.call(this, p, v);
-      this.heading = h;
-      if (lifespan)
-      {
-         this.lifespan = lifespan;
-      }
-      return this;
-   };
-   
-   extend(Asteroids.Bullet, Game.Actor,
-   {
-      BULLET_WIDTH: 2,
-      BULLET_HEIGHT: 6,
-      FADE_LENGTH: 5,
-      
-      /**
-       * Bullet heading
-       */
-      heading: 0.0,
-      
-      /**
-       * Bullet lifespan remaining
-       */
-      lifespan: 40,
-      
-      /**
-       * Bullet power energy
-       */
-      powerLevel: 1,
-      
-      /**
-       * Bullet rendering method
-       * 
-       * @param ctx {object} Canvas rendering context
-       */
-      onRender: function onRender(ctx)
-      {
-         var width = this.BULLET_WIDTH;
-         var height = this.BULLET_HEIGHT;
-         ctx.save();
-         ctx.globalCompositeOperation = "lighter";
-         if (this.lifespan < this.FADE_LENGTH)
-         {
-            // fade out
-            ctx.globalAlpha = (1.0 / this.FADE_LENGTH) * this.lifespan;
-         }
-         if (BITMAPS)
-         {
-            ctx.shadowBlur = 8;
-            ctx.shadowColor = ctx.fillStyle = "rgb(50,255,50)";
-         }
-         else
-         {
-            ctx.shadowColor = ctx.strokeStyle = "rgb(50,255,50)";
-         }
-         // rotate the little bullet rectangle into the correct heading
-         ctx.translate(this.position.x, this.position.y);
-         ctx.rotate(this.heading * RAD);
-         var x = -(width / 2);
-         var y = -(height / 2);
-         if (BITMAPS)
-         {
-            ctx.fillRect(x, y, width, height);
-            ctx.fillRect(x, y+1, width, height-1);
-         }
-         else
-         {
-            ctx.strokeRect(x, y-1, width, height+1);
-            ctx.strokeRect(x, y, width, height);
-         }
-         ctx.restore();
-      },
-      
-      /**
-       * Actor expiration test
-       * 
-       * @return true if expired and to be removed from the actor list, false if still in play
-       */
-      expired: function expired()
-      {
-         // deduct lifespan from the bullet
-         return (--this.lifespan === 0);
-      },
-      
-      /**
-       * Area effect weapon radius - zero for primary bullets
-       */
-      effectRadius: function effectRadius()
-      {
-         return 0;
-      },
-      
-      radius: function radius()
-      {
-         // approximate based on average between width and height
-         return (this.BULLET_HEIGHT + this.BULLET_WIDTH) / 2;
-      },
-      
-      power: function power()
-      {
-         return this.powerLevel;
-      }
-   });
-})();
-
-
-/**
- * Player BulletX2 actor class. Used by the Twin Cannons primary weapon.
- *
- * @namespace Asteroids
- * @class Asteroids.BulletX2
- */
-(function()
-{
-   Asteroids.BulletX2 = function(p, v, h)
-   {
-      Asteroids.BulletX2.superclass.constructor.call(this, p, v, h);
-      this.lifespan = 50;
-      this.powerLevel = 2;
-      return this;
-   };
-   
-   extend(Asteroids.BulletX2, Asteroids.Bullet,
-   {
-      /**
-       * Bullet rendering method
-       * 
-       * @param ctx {object} Canvas rendering context
-       */
-      onRender: function onRender(ctx)
-      {
-         var width = this.BULLET_WIDTH;
-         var height = this.BULLET_HEIGHT;
-         ctx.save();
-         ctx.globalCompositeOperation = "lighter";
-         if (this.lifespan < this.FADE_LENGTH)
-         {
-            // fade out
-            ctx.globalAlpha = (1.0 / this.FADE_LENGTH) * this.lifespan;
-         }
-         if (BITMAPS)
-         {
-            ctx.shadowBlur = 8;
-            ctx.shadowColor = ctx.fillStyle = "rgb(50,255,128)";
-         }
-         else
-         {
-            ctx.shadowColor = ctx.strokeStyle = "rgb(50,255,128)";
-         }
-         // rotate the little bullet rectangle into the correct heading
-         ctx.translate(this.position.x, this.position.y);
-         ctx.rotate(this.heading * RAD);
-         var x = -(width / 2);
-         var y = -(height / 2);
-         if (BITMAPS)
-         {
-            ctx.fillRect(x - 4, y, width, height);
-            ctx.fillRect(x - 4, y+1, width, height-1);
-            ctx.fillRect(x + 4, y, width, height);
-            ctx.fillRect(x + 4, y+1, width, height-1);
-         }
-         else
-         {
-            ctx.strokeRect(x - 4, y-1, width, height+1);
-            ctx.strokeRect(x - 4, y, width, height);
-            ctx.strokeRect(x + 4, y-1, width, height+1);
-            ctx.strokeRect(x + 4, y, width, height);
-         }
-         ctx.restore();
-      },
-      
-      radius: function radius()
-      {
-         // double width bullets - so bigger hit area than basic ones
-         return (this.BULLET_HEIGHT);
-      }
-   });
-})();
-
-
-/**
- * Bomb actor class.
- *
- * @namespace Asteroids
- * @class Asteroids.Bomb
- */
-(function()
-{
-   Asteroids.Bomb = function(p, v)
-   {
-      Asteroids.Bomb.superclass.constructor.call(this, p, v);
-      return this;
-   };
-   
-   extend(Asteroids.Bomb, Asteroids.Bullet,
-   {
-      BOMB_RADIUS: 4,
-      FADE_LENGTH: 5,
-      EFFECT_RADIUS: 45,
-      
-      /**
-       * Bomb lifespan remaining
-       */
-      lifespan: 80,
-      
-      /**
-       * Bomb rendering method
-       * 
-       * @param ctx {object} Canvas rendering context
-       */
-      onRender: function onRender(ctx)
-      {
-         var rad = this.BOMB_RADIUS;
-         ctx.save();
-         ctx.globalCompositeOperation = "lighter";
-         var alpha = 0.8;
-         if (this.lifespan < this.FADE_LENGTH)
-         {
-            // fade out
-            alpha = (0.8 / this.FADE_LENGTH) * this.lifespan;
-            rad = (this.BOMB_RADIUS / this.FADE_LENGTH) * this.lifespan;
-         }
-         ctx.globalAlpha = alpha;
-         if (BITMAPS)
-         {
-            ctx.fillStyle = "rgb(155,255,155)";
-         }
-         else
-         {
-            ctx.shadowColor = ctx.strokeStyle = "rgb(155,255,155)";
-         }
-         ctx.translate(this.position.x, this.position.y);
-         ctx.rotate(GameHandler.frameCount % 360);
-         // account for the fact that stroke() draws around the shape
-         if (!BITMAPS) ctx.scale(0.8, 0.8);
-         ctx.beginPath()
-         ctx.moveTo(rad * 2, 0);
-         for (var i=0; i<15; i++)
-         {
-            ctx.rotate(Math.PI / 8);
-            if (i % 2 == 0)
-            {
-               ctx.lineTo((rad * 2 / 0.525731) * 0.200811, 0);
-            }
-            else
-            {
-               ctx.lineTo(rad * 2, 0);
-            }
-         }
-         ctx.closePath();
-         if (BITMAPS) ctx.fill(); else ctx.stroke();
-         ctx.restore();
-      },
-      
-      /**
-       * Area effect weapon radius
-       */
-      effectRadius: function effectRadius()
-      {
-         return this.EFFECT_RADIUS;
-      },
-      
-      radius: function radius()
-      {
-         var rad = this.BOMB_RADIUS;
-         if (this.lifespan <= this.FADE_LENGTH)
-         {
-            rad = (this.BOMB_RADIUS / this.FADE_LENGTH) * this.lifespan;
-         }
-         return rad;
-      }
-   });
-})();
-
-
-/**
- * Enemy Bullet actor class.
- *
- * @namespace Asteroids
- * @class Asteroids.EnemyBullet
- */
-(function()
-{
-   Asteroids.EnemyBullet = function(p, v)
-   {
-      Asteroids.EnemyBullet.superclass.constructor.call(this, p, v);
-      return this;
-   };
-   
-   extend(Asteroids.EnemyBullet, Game.Actor,
-   {
-      BULLET_RADIUS: 4,
-      FADE_LENGTH: 5,
-      
-      /**
-       * Bullet lifespan remaining
-       */
-      lifespan: 60,
-      
-      /**
-       * Bullet rendering method
-       * 
-       * @param ctx {object} Canvas rendering context
-       */
-      onRender: function onRender(ctx)
-      {
-         var rad = this.BULLET_RADIUS;
-         ctx.save();
-         ctx.globalCompositeOperation = "lighter";
-         var alpha = 0.7;
-         if (this.lifespan < this.FADE_LENGTH)
-         {
-            // fade out and make smaller
-            alpha = (0.7 / this.FADE_LENGTH) * this.lifespan;
-            rad = (this.BULLET_RADIUS / this.FADE_LENGTH) * this.lifespan;
-         }
-         ctx.globalAlpha = alpha;
-         if (BITMAPS)
-         {
-            ctx.fillStyle = "rgb(150,255,150)";
-         }
-         else
-         {
-            ctx.shadowColor = ctx.strokeStyle = "rgb(150,255,150)";
-         }
-         
-         ctx.beginPath();
-         ctx.arc(this.position.x, this.position.y, (rad-1 > 0 ? rad-1 : 0.1), 0, TWOPI, true);
-         ctx.closePath();
-         if (BITMAPS) ctx.fill(); else ctx.stroke();
-         
-         ctx.translate(this.position.x, this.position.y);
-         ctx.rotate((GameHandler.frameCount % 720) / 2);
-         ctx.beginPath()
-         ctx.moveTo(rad * 2, 0);
-         for (var i=0; i<7; i++)
-         {
-            ctx.rotate(Math.PI/4);
-            if (i%2 == 0)
-            {
-               ctx.lineTo((rad * 2/0.525731) * 0.200811, 0);
-            }
-            else
-            {
-               ctx.lineTo(rad * 2, 0);
-            }
-         }
-         ctx.closePath();
-         if (BITMAPS) ctx.fill(); else ctx.stroke();
-         
-         ctx.restore();
-      },
-      
-      /**
-       * Actor expiration test
-       * 
-       * @return true if expired and to be removed from the actor list, false if still in play
-       */
-      expired: function expired()
-      {
-         // deduct lifespan from the bullet
-         return (--this.lifespan === 0);
-      },
-      
-      radius: function radius()
-      {
-         var rad = this.BULLET_RADIUS;
-         if (this.lifespan <= this.FADE_LENGTH)
-         {
-            rad = (rad / this.FADE_LENGTH) * this.lifespan;
-         }
-         return rad;
-      }
-   });
-})();
-/**
- * Basic explosion effect actor class.
- * 
- * @namespace Asteroids
- * @class Asteroids.Explosion
- */
-(function()
-{
-   Asteroids.Explosion = function(p, v, s)
-   {
-      Asteroids.Explosion.superclass.constructor.call(this, p, v, this.FADE_LENGTH);
-      this.size = s;
-      return this;
-   };
-   
-   extend(Asteroids.Explosion, Game.EffectActor,
-   {
-      FADE_LENGTH: 10,
-      
-      /**
-       * Explosion size
-       */
-      size: 0,
-      
-      /**
-       * Explosion rendering method
-       * 
-       * @param ctx {object} Canvas rendering context
-       */
-      onRender: function onRender(ctx)
-      {
-         // fade out
-         var brightness = Math.floor((255 / this.FADE_LENGTH) * this.lifespan);
-         var rad = (this.size * 8 / this.FADE_LENGTH) * this.lifespan;
-         var rgb = brightness.toString();
-         ctx.save();
-         ctx.globalAlpha = 0.75;
-         ctx.fillStyle = "rgb(" + rgb + ",0,0)";
-         ctx.beginPath();
-         ctx.arc(this.position.x, this.position.y, rad, 0, TWOPI, true);
-         ctx.closePath();
-         ctx.fill();
-         ctx.restore();
-      }
-   });
-})();
-
-
-/**
- * Player Explosion effect actor class.
- * 
- * @namespace Asteroids
- * @class Asteroids.PlayerExplosion
- */
-(function()
-{
-   Asteroids.PlayerExplosion = function(p, v)
-   {
-      Asteroids.PlayerExplosion.superclass.constructor.call(this, p, v, this.FADE_LENGTH);
-      return this;
-   };
-   
-   extend(Asteroids.PlayerExplosion, Game.EffectActor,
-   {
-      FADE_LENGTH: 15,
-      
-      /**
-       * Explosion rendering method
-       * 
-       * @param ctx {object} Canvas rendering context
-       */
-      onRender: function onRender(ctx)
-      {
-         ctx.save();
-         var alpha = (1.0 / this.FADE_LENGTH) * this.lifespan;
-         ctx.globalCompositeOperation = "lighter";
-         ctx.globalAlpha = alpha;
-         
-         var rad;
-         if (this.lifespan > 5 && this.lifespan <= 15)
-         {
-            var offset = this.lifespan - 5;
-            rad = (48 / this.FADE_LENGTH) * offset;
-            ctx.fillStyle = "rgb(255,170,30)";
-            ctx.beginPath();
-            ctx.arc(this.position.x-2, this.position.y-2, rad, 0, TWOPI, true);
-            ctx.closePath();
-            ctx.fill();
-         }
-         
-         if (this.lifespan > 2 && this.lifespan <= 12)
-         {
-            var offset = this.lifespan - 2;
-            rad = (32 / this.FADE_LENGTH) * offset;
-            ctx.fillStyle = "rgb(255,255,50)";
-            ctx.beginPath();
-            ctx.arc(this.position.x+2, this.position.y+2, rad, 0, TWOPI, true);
-            ctx.closePath();
-            ctx.fill();
-         }
-         
-         if (this.lifespan <= 10)
-         {
-            var offset = this.lifespan;
-            rad = (24 / this.FADE_LENGTH) * offset;
-            ctx.fillStyle = "rgb(255,70,100)";
-            ctx.beginPath();
-            ctx.arc(this.position.x+2, this.position.y-2, rad, 0, TWOPI, true);
-            ctx.closePath();
-            ctx.fill();
-         }
-         
-         ctx.restore();
-      }
-   });
-})();
-
-
-/**
- * Impact effect (from bullet hitting an object) actor class.
- * 
- * @namespace Asteroids
- * @class Asteroids.Impact
- */
-(function()
-{
-   Asteroids.Impact = function(p, v)
-   {
-      Asteroids.Impact.superclass.constructor.call(this, p, v, this.FADE_LENGTH);
-      return this;
-   };
-   
-   extend(Asteroids.Impact, Game.EffectActor,
-   {
-      FADE_LENGTH: 12,
-      
-      /**
-       * Impact effect rendering method
-       * 
-       * @param ctx {object} Canvas rendering context
-       */
-      onRender: function onRender(ctx)
-      {
-         // fade out alpha
-         var alpha = (1.0 / this.FADE_LENGTH) * this.lifespan;
-         ctx.save();
-         ctx.globalAlpha = alpha * 0.75;
-         if (BITMAPS)
-         {
-            ctx.fillStyle = "rgb(50,255,50)";
-         }
-         else
-         {
-            ctx.shadowColor = ctx.strokeStyle = "rgb(50,255,50)";
-         }
-         ctx.beginPath();
-         ctx.arc(this.position.x, this.position.y, 2, 0, TWOPI, true);
-         ctx.closePath();
-         if (BITMAPS) ctx.fill(); else ctx.stroke();
-         ctx.globalAlpha = alpha;
-         ctx.beginPath();
-         ctx.arc(this.position.x, this.position.y, 1, 0, TWOPI, true);
-         ctx.closePath();
-         if (BITMAPS) ctx.fill(); else ctx.stroke();
-         ctx.restore();
-      }
-   });
-})();
-
-
-/**
- * Text indicator effect actor class.
- * 
- * @namespace Asteroids
- * @class Asteroids.TextIndicator
- */
-(function()
-{
-   Asteroids.TextIndicator = function(p, v, msg, textSize, colour, fadeLength)
-   {
-      this.fadeLength = (fadeLength ? fadeLength : this.DEFAULT_FADE_LENGTH);
-      Asteroids.TextIndicator.superclass.constructor.call(this, p, v, this.fadeLength);
-      this.msg = msg;
-      if (textSize)
-      {
-         this.textSize = textSize;
-      }
-      if (colour)
-      {
-         this.colour = colour;
-      }
-      return this;
-   };
-   
-   extend(Asteroids.TextIndicator, Game.EffectActor,
-   {
-      DEFAULT_FADE_LENGTH: 16,
-      fadeLength: 0,
-      textSize: 12,
-      msg: null,
-      colour: "rgb(255,255,255)",
-      
-      /**
-       * Text indicator effect rendering method
-       * 
-       * @param ctx {object} Canvas rendering context
-       */
-      onRender: function onRender(ctx)
-      {
-         // fade out alpha
-         var alpha = (1.0 / this.fadeLength) * this.lifespan;
-         ctx.save();
-         ctx.globalAlpha = alpha;
-         Game.fillText(ctx, this.msg, this.textSize + "pt Courier New", this.position.x, this.position.y, this.colour);
-         ctx.restore();
-      }
-   });
-})();
-
-
-/**
- * Score indicator effect actor class.
- * 
- * @namespace Asteroids
- * @class Asteroids.ScoreIndicator
- */
-(function()
-{
-   Asteroids.ScoreIndicator = function(p, v, score, textSize, prefix, colour, fadeLength)
-   {
-      var msg = score.toString();
-      if (prefix)
-      {
-         msg = prefix + ' ' + msg;
-      }
-      Asteroids.ScoreIndicator.superclass.constructor.call(this, p, v, msg, textSize, colour, fadeLength);
-      return this;
-   };
-   
-   extend(Asteroids.ScoreIndicator, Asteroids.TextIndicator,
-   {
-   });
-})();
-
-
-/**
- * Power up collectable.
- * 
- * @namespace Asteroids
- * @class Asteroids.PowerUp
- */
-(function()
-{
-   Asteroids.PowerUp = function(p, v)
-   {
-      Asteroids.PowerUp.superclass.constructor.call(this, p, v);
-      return this;
-   };
-   
-   extend(Asteroids.PowerUp, Game.EffectActor,
-   {
-      RADIUS: 8,
-      pulse: 128,
-      pulseinc: 8,
-      
-      /**
-       * Power up rendering method
-       * 
-       * @param ctx {object} Canvas rendering context
-       */
-      onRender: function onRender(ctx)
-      {
-         ctx.save();
-         ctx.globalAlpha = 0.75;
-         var col = "rgb(255," + this.pulse.toString() + ",0)";
-         if (BITMAPS)
-         {
-            ctx.fillStyle = col;
-            ctx.strokeStyle = "rgb(255,255,128)";
-         }
-         else
-         {
-            ctx.lineWidth = 2.0;
-            ctx.shadowColor = ctx.strokeStyle = col;
-         }
-         ctx.beginPath();
-         ctx.arc(this.position.x, this.position.y, this.RADIUS, 0, TWOPI, true);
-         ctx.closePath();
-         if (BITMAPS)
-         {
-            ctx.fill();
-         }
-         ctx.stroke();
-         ctx.restore();
-         this.pulse += this.pulseinc;
-         if (this.pulse > 255)
-         {
-            this.pulse = 256 - this.pulseinc;
-            this.pulseinc =- this.pulseinc;
-         }
-         else if (this.pulse < 0)
-         {
-            this.pulse = 0 - this.pulseinc;
-            this.pulseinc =- this.pulseinc;
-         }
-      },
-      
-      radius: function radius()
-      {
-         return this.RADIUS;
-      },
-      
-      collected: function collected(game, player, scene)
-      {
-         // randomly select a powerup to apply
-         var message = null;
-         switch (randomInt(0, 9))
-         {
-            case 0:
-            case 1:
-               // boost energy
-               message = "Energy Boost!";
-               player.energy += player.ENERGY_INIT/2;
-               if (player.energy > player.ENERGY_INIT)
-               {
-                  player.energy = player.ENERGY_INIT;
-               }
-               break;
-            
-            case 2:
-               // fire when shieled
-               message = "Fire When Shielded!";
-               player.fireWhenShield = true;
-               break;
-            
-            case 3:
-               // extra life
-               message = "Extra Life!";
-               game.lives++;
-               break;
-            
-            case 4:
-               // slow down asteroids
-               message = "Slow Down Asteroids!";
-               for (var n = 0, m = scene.enemies.length, enemy; n < m; n++)
-               {
-                  enemy = scene.enemies[n];
-                  if (enemy instanceof Asteroids.Asteroid)
-                  {
-                     enemy.vector.scale(0.75);
-                  }
-               }
-               break;
-            
-            case 5:
-               // smart bomb
-               message = "Smart Bomb!";
-               
-               var effectRad = 96;
-               
-               // add a BIG explosion actor at the smart bomb weapon position and vector
-               var boom = new Asteroids.Explosion(
-                     this.position.clone(), this.vector.clone().scale(0.5), effectRad / 8);
-               scene.effects.push(boom);
-               
-               // test circle intersection with each enemy actor
-               // we check the enemy list length each iteration to catch baby asteroids
-               // this is a fully fledged smart bomb after all!
-               for (var n = 0, enemy, pos = this.position; n < scene.enemies.length; n++)
-               {
-                  enemy = scene.enemies[n];
-                  
-                  // test the distance against the two radius combined
-                  if (pos.distance(enemy.position) <= effectRad + enemy.radius())
-                  {
-                     // intersection detected! 
-                     enemy.hit(-1);
-                     scene.generatePowerUp(enemy);
-                     scene.destroyEnemy(enemy, this.vector, true);
-                  }
-               }
-               break;
-            
-            case 6:
-               // twin cannon primary weapon upgrade
-               message = "Twin Cannons!";
-               player.primaryWeapons["main"] = new Asteroids.TwinCannonsWeapon(player);
-               break;
-            
-            case 7:
-               // v spray cannons
-               message = "Spray Cannons!";
-               player.primaryWeapons["main"] = new Asteroids.VSprayCannonsWeapon(player);
-               break;
-            
-            case 8:
-               // rear guns
-               message = "Rear Gun!";
-               player.primaryWeapons["rear"] = new Asteroids.RearGunWeapon(player);
-               break;
-            
-            case 9:
-               // side guns
-               message = "Side Guns!";
-               player.primaryWeapons["side"] = new Asteroids.SideGunWeapon(player);
-               break;
-         }
-         
-         if (message)
-         {
-            // generate a effect indicator at the destroyed enemy position
-            var vec = new Vector(0, -3.0);
-            var effect = new Asteroids.TextIndicator(
-                  new Vector(this.position.x, this.position.y - this.RADIUS), vec, message, null, null, 32);
-            scene.effects.push(effect);
-         }
-      }
-   });
-})();
-/**
- * Arena5 HTML canvas game.
- * Scenes for CanvasMark Rendering Benchmark - March 2013
- *  
- * (C) 2013 Kevin Roast kevtoast@yahoo.com @kevinroast
- * 
- * Please see: license.txt
- * You are welcome to use this code, but I would appreciate an email or tweet
- * if you do anything interesting with it!
- */
-
-
-/**
- * Arena root namespace.
- * 
- * @namespace Arena
- */
-if (typeof Arena == "undefined" || !Arena)
-{
-   var Arena = {};
-}
-
-
-/**
- * Arena prerenderer class.
- * 
- * @namespace Arena
- * @class Arena.Prerenderer
- */
-(function()
-{
-   Arena.Prerenderer = function()
-   {
-      this.images = [];
-      this._renderers = [];
-      return this;
-   };
-   
-   Arena.Prerenderer.prototype =
-   {
-      /**
-       * Image list. Keyed by renderer ID - returning an array also. So to get
-       * the first image output by prerenderer with id "default": images["default"][0]
-       * 
-       * @public
-       * @property images
-       * @type Array
-       */
-      images: null,
-      
-      _renderers: null,
-      
-      /**
-       * Add a renderer function to the list of renderers to execute
-       * 
-       * @param fn {function}    Callback to execute to perform prerender
-       *                         Passed canvas element argument - to execute against - the
-       *                         callback is responsible for setting appropriate width/height
-       *                         of the buffer and should not assume it is cleared.
-       *                         Should return Array of images from prerender process
-       * @param id {string}      Id of the prerender - used to lookup images later
-       */
-      addRenderer: function addRenderer(fn, id)
-      {
-         this._renderers[id] = fn;
-      },
-      
-      /**
-       * Execute all prerender functions - call once all renderers have been added
-       */
-      execute: function execute()
-      {
-         var buffer = document.createElement('canvas');
-         for (var id in this._renderers)
-         {
-            this.images[id] = this._renderers[id].call(this, buffer);
-         }
-      }
-    };
-})();
-
-
-/**
- * Arena main Benchmark Test class.
- * 
- * @namespace Arena
- * @class Arena.Test
- */
-(function()
-{
-   Arena.Test = function(benchmark)
-   {
-      // generate the single player actor - available across all scenes
-      this.player = new Arena.Player(new Vector(GameHandler.width / 2, GameHandler.height / 2), new Vector(0, 0), 0);
-      
-      // add benchmark scene
-      benchmark.addBenchmarkScene(new Arena.BenchmarkScene(this, benchmark.scenes.length, 0));
-      
-      // perform prerender steps - create bitmap graphics to use later
-      var pr = new Arena.Prerenderer();
-      // function to generate a set of point particle images
-      var fnPointRenderer = function(buffer, colour)
-         {
-            var imgs = [];
-            for (var size=4; size<=10; size+=2)
-            {
-               var width = size << 1;
-               buffer.width = buffer.height = width;
-               var ctx = buffer.getContext('2d');
-               var radgrad = ctx.createRadialGradient(size, size, size >> 1, size, size, size);  
-               radgrad.addColorStop(0, colour);
-               radgrad.addColorStop(1, "#000");
-               ctx.fillStyle = radgrad;
-               ctx.fillRect(0, 0, width, width);
-               var img = new Image();
-               img.src = buffer.toDataURL("image/png");
-               imgs.push(img);
-            }
-            return imgs;
-         };
-      // add the various point particle image prerenderers based on above function
-      // default explosion colour
-      pr.addRenderer(function(buffer) {
-            return fnPointRenderer.call(this, buffer, "rgb(255,125,50)");
-         }, "points_rgb(255,125,50)");
-      // Tracker: enemy particles
-      pr.addRenderer(function(buffer) {
-            return fnPointRenderer.call(this, buffer, "rgb(255,96,0)");
-         }, "points_rgb(255,96,0)");
-      // Borg: enemy particles
-      pr.addRenderer(function(buffer) {
-            return fnPointRenderer.call(this, buffer, "rgb(0,255,64)");
-         }, "points_rgb(0,255,64)");
-      // Splitter: enemy particles
-      pr.addRenderer(function(buffer) {
-            return fnPointRenderer.call(this, buffer, "rgb(148,0,255)");
-         }, "points_rgb(148,0,255)");
-      // Bomber: enemy particles
-      pr.addRenderer(function(buffer) {
-            return fnPointRenderer.call(this, buffer, "rgb(255,0,255)");
-         }, "points_rgb(255,0,255)");
-      // add the smudge explosion particle image prerenderer
-      pr.addRenderer(function(buffer) {
-            var imgs = [];
-            for (var size=8; size<=64; size+=8)
-            {
-               var width = size << 1;
-               buffer.width = buffer.height = width;
-               var ctx = buffer.getContext('2d');
-               var radgrad = ctx.createRadialGradient(size, size, size >> 3, size, size, size);  
-               radgrad.addColorStop(0, "rgb(255,125,50)");
-               radgrad.addColorStop(1, "#000");
-               ctx.fillStyle = radgrad;
-               ctx.fillRect(0, 0, width, width);
-               var img = new Image();
-               img.src = buffer.toDataURL("image/png");
-               imgs.push(img);
-            }
-            return imgs;
-         }, "smudges");
-      pr.addRenderer(function(buffer) {
-            var imgs = [];
-            var size = 40;
-            buffer.width = buffer.height = size;
-            var ctx = buffer.getContext('2d');
-            
-            // draw bullet primary weapon
-            var rf = function(width, height)
-            {
-               ctx.beginPath();
-               ctx.moveTo(0, height);
-               ctx.lineTo(width, 0);
-               ctx.lineTo(width, -height);
-               ctx.lineTo(0, -height*0.5);
-               ctx.lineTo(-width, -height);
-               ctx.lineTo(-width, 0);
-               ctx.closePath();
-               ctx.fill();
-            };
-            ctx.shadowBlur = 8;
-            ctx.globalCompositeOperation = "lighter";
-            ctx.translate(size/2, size/2);
-            ctx.shadowColor = "rgb(255,255,255)";
-            ctx.fillStyle = "rgb(255,220,75)";
-            rf.call(this, 10, 15)
-            ctx.shadowColor = "rgb(255,100,100)";
-            ctx.fillStyle = "rgb(255,50,50)";
-            rf.call(this, 10 * 0.75, 15 * 0.75);
-            
-            var img = new Image();
-            img.src = buffer.toDataURL("image/png");
-            imgs.push(img);
-            return imgs;
-         }, "playerweapon");
-      pr.execute();
-      GameHandler.prerenderer = pr;
-   };
-   
-   Arena.Test.prototype =
-   {
-      /**
-       * Reference to the single game player actor
-       */
-      player: null,
-      
-      /**
-       * Lives count
-       */
-      lives: 1,
-      
-      /**
-       * Current game score 
-       */
-      score: 0,
-      
-      /**
-       * High score
-       */
-      highscore: 0,
-      
-      /**
-       * Last score
-       */
-      lastscore: 0,
-      
-      /**
-       * Current multipler
-       */
-      scoreMultiplier: 1
-   };
-})();
-
-
-/**
- * Arena Game scene class.
- * 
- * @namespace Arena
- * @class Arena.BenchmarkScene
- */
-(function()
-{
-   Arena.BenchmarkScene = function(game, test, feature)
-   {
-      this.game = game;
-      this.test = test;
-      this.feature = feature;
-      this.player = game.player;
-      
-      var msg = "Test " + test + " - Arena5 - Vectors, shadows, bitmaps, text";
-      var interval = new Game.Interval(msg, this.intervalRenderer);
-      Arena.BenchmarkScene.superclass.constructor.call(this, true, interval);
-   };
-   
-   extend(Arena.BenchmarkScene, Game.Scene,
-   {
-      test: 0,
-      game: null,
-      
-      /**
-       * Local reference to the game player actor
-       */
-      player: null,
-      
-      /**
-       * Top-level list of game actors sub-lists
-       */
-      actors: null,
-      
-      /**
-       * List of player fired bullet actors
-       */
-      playerBullets: null,
-      
-      /**
-       * List of enemy actors (asteroids, ships etc.)
-       */
-      enemies: null,
-      
-      /**
-       * List of enemy fired bullet actors
-       */
-      enemyBullets: null,
-      
-      /**
-       * List of effect actors
-       */
-      effects: null,
-      
-      /**
-       * List of collectables actors
-       */
-      collectables: null,
-      
-      /**
-       * Displayed score (animates towards actual score)
-       */
-      scoredisplay: 0,
-      
-      /**
-       * Scene init event handler
-       */
-      onInitScene: function onInitScene()
-      {
-         // generate the actors and add the actor sub-lists to the main actor list
-         this.actors = [];
-         this.actors.push(this.enemies = []);
-         this.actors.push(this.playerBullets = []);
-         this.actors.push(this.enemyBullets = []);
-         this.actors.push(this.effects = []);
-         this.actors.push(this.collectables = []);
-         
-         // start view centered in the game world
-         this.world.viewx = this.world.viewy = (this.world.size / 2) - (this.world.viewsize / 2);
-         
-         this.testScore = 10;
-         
-         // reset player
-         this.resetPlayerActor();
-      },
-      
-      /**
-       * Restore the player to the game - reseting position etc.
-       */
-      resetPlayerActor: function resetPlayerActor(persistPowerUps)
-      {
-         this.actors.push([this.player]);
-         
-         // reset the player position - centre of world
-         with (this.player)
-         {
-            position.x = position.y = this.world.size / 2;
-            vector.x = vector.y = heading = 0;
-            reset(persistPowerUps);
-         }
-      },
-      
-      /**
-       * Scene before rendering event handler
-       */
-      onBeforeRenderScene: function onBeforeRenderScene(benchmark)
-      {
-         var p = this.player,
-             w = this.world;
-         
-         // update view position based on new player position
-         var viewedge = w.viewsize * 0.2;
-         if (p.position.x > viewedge && p.position.x < w.size - viewedge)
-         {
-            w.viewx = p.position.x - w.viewsize * 0.5;
-         }
-         if (p.position.y > viewedge && p.position.y < w.size - viewedge)
-         {
-            w.viewy = p.position.y - w.viewsize * 0.5;
-         }
-         
-         if (benchmark)
-         {
-            if (Date.now() - this.sceneStartTime > this.testState)
-            {
-               this.testState += 500;
-               for (var i=0; i<2; i++)
-               {
-                  this.enemies.push(new Arena.EnemyShip(this, (this.enemies.length % 6) + 1));
-               }
-               this.enemies[0].damageBy(100);
-               this.destroyEnemy(this.enemies[0], new Vector(0,1), this.player)
-            }
-         }
-         
-         // update all actors using their current vector in the game world
-         this.updateActors();
-      },
-      
-      /**
-       * Scene rendering event handler
-       */
-      onRenderScene: function onRenderScene(ctx)
-      {
-         ctx.clearRect(0, 0, GameHandler.width, GameHandler.height);
-         
-         // glowing vector effect shadow
-         ctx.shadowBlur = (DEBUG && DEBUG.DISABLEGLOWEFFECT) ? 0 : 8;
-         
-         // render background effect - wire grid
-         this.renderBackground(ctx);
-         
-         // render the game actors
-         this.renderActors(ctx);
-         
-         // render info overlay graphics
-         this.renderOverlay(ctx);
-         
-         // detect bullet collisions
-         this.collisionDetectBullets();
-      },
-      
-      /**
-       * Render background effects for the scene
-       */
-      renderBackground: function renderBackground(ctx)
-      {
-         // render background effect - wire grid
-         // manually transform world to screen for this effect and therefore
-         // assume there is a horizonal and vertical "wire" every N units
-         ctx.save();
-         ctx.strokeStyle = "rgb(0,30,60)";
-         ctx.lineWidth = 1.0;
-         ctx.shadowBlur = 0;
-         ctx.beginPath();
-         
-         var UNIT = 100;
-         var w = this.world;
-             xoff = UNIT - w.viewx % UNIT,
-             yoff = UNIT - w.viewy % UNIT,
-             // calc top left edge of world (prescaled)
-             x1 = (w.viewx >= 0 ? 0 : -w.viewx) * w.scale,
-             y1 = (w.viewy >= 0 ? 0 : -w.viewy) * w.scale,
-             // calc bottom right edge of world (prescaled)
-             x2 = (w.viewx < w.size - w.viewsize ? w.viewsize : w.size - w.viewx) * w.scale,
-             y2 = (w.viewy < w.size - w.viewsize ? w.viewsize : w.size - w.viewy) * w.scale;
-         
-         // plot the grid wires that make up the background
-         for (var i=0, j=w.viewsize/UNIT; i<j; i++)
-         {
-            // check we are in bounds of the visible world before drawing grid line segments
-            if (xoff + w.viewx > 0 && xoff + w.viewx < w.size)
-            {
-               ctx.moveTo(xoff * w.scale, y1);
-               ctx.lineTo(xoff * w.scale, y2);
-            }
-            if (yoff + w.viewy > 0 && yoff + w.viewy < w.size)
-            {
-               ctx.moveTo(x1, yoff * w.scale);
-               ctx.lineTo(x2, yoff * w.scale);
-            }
-            xoff += UNIT;
-            yoff += UNIT;
-         }
-         
-         ctx.closePath();
-         ctx.stroke();
-         
-         // render world edges
-         ctx.strokeStyle = "rgb(60,128,90)";
-         ctx.lineWidth = 1;
-         ctx.beginPath();
-         
-         if (w.viewx <= 0)
-         {
-            xoff = -w.viewx;
-            ctx.moveTo(xoff * w.scale, y1);
-            ctx.lineTo(xoff * w.scale, y2);
-         }
-         else if (w.viewx >= w.size - w.viewsize)
-         {
-            xoff = w.size - w.viewx;
-            ctx.moveTo(xoff * w.scale, y1);
-            ctx.lineTo(xoff * w.scale, y2);
-         }
-         if (w.viewy <= 0)
-         {
-            yoff = -w.viewy;
-            ctx.moveTo(x1, yoff * w.scale);
-            ctx.lineTo(x2, yoff * w.scale);
-         }
-         else if (w.viewy >= w.size - w.viewsize)
-         {
-            yoff = w.size - w.viewy;
-            ctx.moveTo(x1, yoff * w.scale);
-            ctx.lineTo(x2, yoff * w.scale);
-         }
-         
-         ctx.closePath();
-         ctx.stroke();
-         ctx.restore();
-      },
-      
-      /**
-       * Update the scene actors based on current vectors and expiration.
-       */
-      updateActors: function updateActors()
-      {
-         for (var i = 0, j = this.actors.length; i < j; i++)
-         {
-            var actorList = this.actors[i];
-            
-            for (var n = 0; n < actorList.length; n++)
-            {
-               var actor = actorList[n];
-               
-               // call onUpdate() event for each actor
-               actor.onUpdate(this);
-               
-               // expiration test first
-               if (actor.expired())
-               {
-                  actorList.splice(n, 1);
-               }
-               else
-               {
-                  // update actor using its current vector
-                  actor.position.add(actor.vector);
-                  
-                  // TODO: different behavior for traversing out of the world space?
-                  //       add behavior flag to Actor i.e. bounce, invert, disipate etc.
-                  //       - could add method to actor itself - so would handle internally...
-                  if (actor === this.player)
-                  {
-                     if (actor.position.x >= this.world.size ||
-                         actor.position.x < 0 ||
-                         actor.position.y >= this.world.size ||
-                         actor.position.y < 0)
-                     {
-                        actor.vector.invert();
-                        actor.vector.scale(0.75);
-                        actor.position.add(actor.vector);
-                     }
-                  }
-                  else
-                  {
-                     var bounceX = false,
-                         bounceY = false;
-                     if (actor.position.x >= this.world.size)
-                     {
-                        actor.position.x = this.world.size;
-                        bounceX = true;
-                     }
-                     else if (actor.position.x < 0)
-                     {
-                        actor.position.x = 0;
-                        bounceX = true;
-                     }
-                     if (actor.position.y >= this.world.size)
-                     {
-                        actor.position.y = this.world.size;
-                        bounceY = true;
-                     }
-                     else if (actor.position.y < 0)
-                     {
-                        actor.position.y = 0;
-                        bounceY = true
-                     }
-                     // bullets don't bounce - create an effect at the arena boundry instead
-                     if ((bounceX || bounceY) &&
-                         ((actor instanceof Arena.Bullet && !this.player.bounceWeapons) ||
-                          actor instanceof Arena.EnemyBullet))
-                     {
-                        // replace bullet with a particle effect at the same position and vector
-                        var vec = actor.vector.nscale(0.5);
-                        this.effects.push(new Arena.BulletImpactEffect(actor.position.clone(), vec));
-                        // remove bullet actor from play
-                        actorList.splice(n, 1);
-                     }
-                     else
-                     {
-                        if (bounceX)
-                        {
-                           var h = actor.vector.thetaTo2(new Vector(0, 1));
-                           actor.vector.rotate(h*2);
-                           actor.vector.scale(0.9);
-                           actor.position.add(actor.vector);
-                           // TODO: add "interface" for actor with heading?
-                           //       or is hasProperty() more "javascript"
-                           if (actor.hasOwnProperty("heading")) actor.heading += (h*2)/RAD;
-                        }
-                        if (bounceY)
-                        {
-                           var h = actor.vector.thetaTo2(new Vector(1, 0));
-                           actor.vector.rotate(h*2);
-                           actor.vector.scale(0.9);
-                           actor.position.add(actor.vector);
-                           if (actor.hasOwnProperty("heading")) actor.heading += (h*2)/RAD;
-                        }
-                     }
-                  }
-               }
-            }
-         }
-      },
-      
-      /**
-       * Detect bullet collisions with enemy actors.
-       */
-      collisionDetectBullets: function collisionDetectBullets()
-      {
-         var bullet, bulletRadius, bulletPos;
-         
-         // collision detect player bullets with enemies
-         // NOTE: test length each loop as list length can change
-         for (var i = 0; i < this.playerBullets.length; i++)
-         {
-            bullet = this.playerBullets[i];
-            bulletRadius = bullet.radius;
-            bulletPos = bullet.position;
-            
-            // test circle intersection with each enemy actor
-            for (var n = 0, m = this.enemies.length, enemy, z; n < m; n++)
-            {
-               enemy = this.enemies[n];
-               
-               // test the distance against the two radius combined
-               if (bulletPos.distance(enemy.position) <= bulletRadius + enemy.radius)
-               {
-                  // test for area effect bomb weapon
-                  var effectRad = bullet.effectRadius();
-                  //if (effectRad === 0)
-                  {
-                     // impact the enemy with the bullet - may destroy it or just damage it
-                     if (enemy.damageBy(bullet.power()))
-                     {
-                        // destroy the enemy under the bullet
-                        this.destroyEnemy(enemy, bullet.vector, true);
-                        this.generateMultiplier(enemy);
-                        this.generatePowerUp(enemy);
-                     }
-                     else
-                     {
-                        // add bullet impact effect to show the bullet hit
-                        var effect = new Arena.EnemyImpact(
-                           bullet.position.clone(),
-                           bullet.vector.nscale(0.5 + Rnd() * 0.5), enemy);
-                        this.effects.push(effect);
-                     }
-                  }
-                  
-                  // remove this bullet from the actor list as it has been destroyed
-                  this.playerBullets.splice(i, 1);
-                  break;
-               }
-            }
-         }
-      },
-      
-      /**
-       * Destroy an enemy. Replace with appropriate effect.
-       * Also applies the score for the destroyed item if the player caused it.
-       * 
-       * @param enemy {Game.EnemyActor} The enemy to destory and add score for
-       * @param parentVector {Vector} The vector of the item that hit the enemy
-       * @param player {boolean} If true, the player was the destroyer
-       */
-      destroyEnemy: function destroyEnemy(enemy, parentVector, player)
-      {
-         // add an explosion actor at the enemy position and vector
-         var vec = enemy.vector.clone();
-         // add scaled parent vector - to give some momentum from the impact
-         vec.add(parentVector.nscale(0.2));
-         this.effects.push(new Arena.EnemyExplosion(enemy.position.clone(), vec, enemy));
-         
-         if (player)
-         {
-            // increment score
-            var inc = (enemy.scoretype + 1) * 5 * this.game.scoreMultiplier;
-            this.game.score += inc;
-            
-            // generate a score effect indicator at the destroyed enemy position
-            var vec = new Vector(0, -5.0).add(enemy.vector.nscale(0.5));
-            this.effects.push(new Arena.ScoreIndicator(
-                  new Vector(enemy.position.x, enemy.position.y - 16), vec, inc));
-            
-            // call event handler for enemy
-            enemy.onDestroyed(this, player);
-         }
-      },
-      
-      /**
-       * Generate score multiplier(s) for player to collect after enemy is destroyed
-       */
-      generateMultiplier: function generateMultiplier(enemy)
-      {
-         if (enemy.dropsMutliplier)
-         {
-            var count = randomInt(1, (enemy.type < 5 ? enemy.type : 4));
-            for (var i=0; i<count; i++)
-            {
-               this.collectables.push(new Arena.Multiplier(enemy.position.clone(),
-                  enemy.vector.nscale(0.2).rotate(Rnd() * TWOPI)));
-            }
-         }
-      },
-      
-      /**
-       * Generate powerup for player to collect after enemy is destroyed
-       */
-      generatePowerUp: function generatePowerUp(enemy)
-      {
-         if (this.player.energy !== this.player.ENERGY_INIT && Rnd() < 0.1)
-         {
-            this.collectables.push(new Arena.EnergyBoostPowerup(enemy.position.clone(),
-               enemy.vector.nscale(0.5).rotate(Rnd() * TWOPI)));
-         }
-      },
-      
-      /**
-       * Render each actor to the canvas.
-       * 
-       * @param ctx {object} Canvas rendering context
-       */
-      renderActors: function renderActors(ctx)
-      {
-         for (var i = 0, j = this.actors.length; i < j; i++)
-         {
-            // walk each sub-list and call render on each object
-            var actorList = this.actors[i];
-            
-            for (var n = actorList.length - 1; n >= 0; n--)
-            {
-               actorList[n].onRender(ctx, this.world);
-            }
-         }
-      },
-      
-      /**
-       * Render player information HUD overlay graphics.
-       * 
-       * @param ctx {object} Canvas rendering context
-       */
-      renderOverlay: function renderOverlay(ctx)
-      {
-         var w = this.world,
-             width = GameHandler.width,
-             height = GameHandler.height;
-         
-         ctx.save();
-         ctx.shadowBlur = 0;
-         
-         // energy bar (scaled down from player energy max)
-         var ewidth = ~~(100 * w.scale * 2),
-             eheight = ~~(4 * w.scale * 2);
-         ctx.strokeStyle = "rgb(128,128,50)";
-         ctx.strokeRect(4, 4, ewidth+1, 4 + eheight);
-         ctx.fillStyle = "rgb(255,255,150)";
-         ctx.fillRect(5, 5, (this.player.energy / (this.player.ENERGY_INIT / ewidth)), 3 + eheight);
-         
-         // score display - update towards the score in increments to animate it
-         var font12pt = Game.fontFamily(w, 12),
-             font12size = Game.fontSize(w, 12);
-         var score = this.game.score,
-             inc = (score - this.scoredisplay) * 0.1;
-         this.scoredisplay += inc;
-         if (this.scoredisplay > score)
-         {
-            this.scoredisplay = score;
-         }
-         var sscore = Ceil(this.scoredisplay).toString();
-         // pad with zeros
-         for (var i=0, j=8-sscore.length; i<j; i++)
-         {
-            sscore = "0" + sscore;
-         }
-         Game.fillText(ctx, sscore, font12pt, width * 0.2 + width * 0.1, font12size + 2, "white");
-         
-         // high score
-         // TODO: add method for incrementing score so this is not done here
-         if (score > this.game.highscore)
-         {
-            this.game.highscore = score;
-         }
-         sscore = this.game.highscore.toString();
-         // pad with zeros
-         for (var i=0, j=8-sscore.length; i<j; i++)
-         {
-            sscore = "0" + sscore;
-         }
-         Game.fillText(ctx, "HI: " + sscore, font12pt, width * 0.4 + width * 0.1, font12size + 2, "white");
-         
-         // score multiplier indicator
-         Game.fillText(ctx, "x" + this.game.scoreMultiplier, font12pt, width * 0.7 + width * 0.1, font12size + 2, "white");
-         
-         // Benchmark - information output
-         if (this.sceneCompletedTime)
-         {
-            Game.fillText(ctx, "TEST " + this.test + " COMPLETED: " + this.getTransientTestScore(), "20pt Courier New", 4, 40, "white");
-         }
-         Game.fillText(ctx, "SCORE: " + this.getTransientTestScore(), "12pt Courier New", 0, GameHandler.height - 42, "lightblue");
-         Game.fillText(ctx, "TSF: " + Math.round(GameHandler.frametime) + "ms", "12pt Courier New", 0, GameHandler.height - 22, "lightblue");
-         Game.fillText(ctx, "FPS: " + GameHandler.lastfps, "12pt Courier New", 0, GameHandler.height - 2, "lightblue");
-         
-         ctx.restore();
-      },
-      
-      screenCenterVector: function screenCenterVector()
-      {
-         // transform to world position - to get the center of the game screen
-         var m = new Vector(GameHandler.width*0.5, GameHandler.height*0.5);
-         m.scale(1 / this.world.scale);
-         m.x += this.world.viewx;
-         m.y += this.world.viewy;
-         return m;
-      }
-   });
-})();
-/**
- * Arena.K3DController class.
- * 
- * Arena impl of a K3D controller. One per sprite.
- */
-(function()
-{
-   /**
-    * Arena.Controller constructor
-    * 
-    * @param canvas {Object}  The canvas to render the object list into.
-    */
-   Arena.Controller = function()
-   {
-      Arena.Controller.superclass.constructor.call(this);
-   };
-   
-   extend(Arena.Controller, K3D.BaseController,
-   {
-      /**
-       * Render tick - should be called from appropriate sprite renderer
-       */
-      render: function(ctx)
-      {
-         // execute super class method to process render pipelines
-         this.processFrame(ctx);
-      }
-   });
-})();
-
-
-/**
- * K3DActor class.
- * 
- * An actor that can be rendered by K3D. The code implements a K3D controller.
- * Call renderK3D() each frame.
- * 
- * @namespace Arena
- * @class Arena.K3DActor
- */
-(function()
-{
-   Arena.K3DActor = function(p, v)
-   {
-      Arena.K3DActor.superclass.constructor.call(this, p, v);
-      this.k3dController = new Arena.Controller();
-      return this;
-   };
-   
-   extend(Arena.K3DActor, Game.Actor,
-   {
-      k3dController: null,
-      k3dObject: null,
-      
-      /**
-       * Render K3D graphic.
-       */
-      renderK3D: function renderK3D(ctx)
-      {
-         this.k3dController.render(ctx);
-      },
-      
-      setK3DObject: function setK3DObject(obj)
-      {
-         this.k3dObject = obj;
-         this.k3dController.addK3DObject(obj);
-      }
-   });
-})();/**
- * Player actor class.
- *
- * @namespace Arena
- * @class Arena.Player
- */
-(function()
-{
-   Arena.Player = function(p, v, h)
-   {
-      Arena.Player.superclass.constructor.call(this, p, v);
-      
-      this.energy = this.ENERGY_INIT;
-      this.radius = 20;
-      this.heading = h;
-      
-      // setup weapons
-      this.primaryWeapons = [];
-      this.primaryWeapons["main"] = new Arena.PrimaryWeapon(this);
-      
-      // 3D sprite object - must be created after constructor call
-      var obj = new K3D.K3DObject();
-      with (obj)
-      {
-         drawmode = "wireframe";
-         shademode = "depthcue";
-         depthscale = 32;
-         linescale = 3;
-         perslevel = 256;
-         
-         addphi = -1.0; //addphi = 1.0; addtheta = -1.0; addgamma = -0.75;
-         scale = 0.8;    // TODO: pre-scale points? (this is only done once for player, but enemies...)
-         init(
-            [{x:-30,y:-20,z:0}, {x:-15,y:-25,z:20}, {x:15,y:-25,z:20}, {x:30,y:-20,z:0}, {x:15,y:-25,z:-20}, {x:-15,y:-25,z:-20}, {x:0,y:35,z:0}],
-            [{a:0,b:1}, {a:1,b:2}, {a:2,b:3}, {a:3,b:4}, {a:4,b:5}, {a:5,b:0}, {a:1,b:6}, {a:2,b:6}, {a:4,b:6}, {a:5,b:6}, {a:0,b:6}, {a:3,b:6}],
-            [{vertices:[0,1,6]}, {vertices:[1,2,6]}, {vertices:[2,3,6]}, {vertices:[3,4,6]}, {vertices:[4,5,6]}, {vertices:[5,0,6]}, {vertices:[0,1,2,3,4,5]}]
-         );
-      }
-      this.setK3DObject(obj);
-      
-      return this;
-   };
-   
-   extend(Arena.Player, Arena.K3DActor,
-   {
-      MAX_PLAYER_VELOCITY: 15.0,
-      THRUST_DELAY: 1,
-      ENERGY_INIT: 100,
-      
-      /**
-       * Player heading
-       */
-      heading: 0,
-      
-      /**
-       * Player energy level
-       */
-      energy: 0,
-      
-      /**
-       * Primary weapon list
-       */
-      primaryWeapons: null,
-      
-      /**
-       * Engine thrust recharge counter
-       */
-      thrustRecharge: 0,
-      
-      /**
-       * True if the engine thrust graphics should be rendered next frame
-       */
-      engineThrust: false,
-      
-      /**
-       * Frame that the player was killed on - to cause a delay before respawning the player
-       */
-      killedOnFrame: 0,
-      
-      /**
-       * Power up settings - primary weapon bounce
-       */
-      bounceWeapons: false,
-      
-      /**
-       * Player rendering method
-       * 
-       * @param ctx {object} Canvas rendering context
-       * @param world {object} World metadata
-       */
-      onRender: function onRender(ctx, world)
-      {
-         var headingRad = this.heading * RAD;
-         
-         // transform world to screen - non-visible returns null
-         var viewposition = Game.worldToScreen(this.position, world, this.radius);
-         if (viewposition)
-         {
-            // render engine thrust?
-            if (this.engineThrust)
-            {
-               ctx.save();
-               // scale ALL graphics... - translate to position apply canvas scaling
-               ctx.translate(viewposition.x, viewposition.y);
-               ctx.scale(world.scale, world.scale);
-               ctx.rotate(headingRad);
-               ctx.translate(0, -4);   // slight offset so that collision radius is centered
-               ctx.globalAlpha = 0.4 + Rnd() * 0.5;
-               ctx.shadowColor = ctx.fillStyle = "rgb(25,125,255)";
-               ctx.beginPath();
-               ctx.moveTo(-12, 20);
-               ctx.lineTo(12, 20);
-               ctx.lineTo(0, 50 + Rnd() * 20);
-               ctx.closePath();
-               ctx.fill();
-               ctx.restore();
-               this.engineThrust = false;
-            }
-            
-            // render player graphic
-            ctx.save();
-            ctx.shadowColor = "rgb(255,255,255)";
-            ctx.translate(viewposition.x, viewposition.y);
-            ctx.scale(world.scale, world.scale);
-            ctx.rotate(headingRad);
-            ctx.translate(0, -4);   // slight offset so that collision radius is centered
-            
-            // render 3D sprite
-            this.renderK3D(ctx);
-            
-            ctx.restore();
-         }
-         //if (DEBUG && !viewposition) console.log("non-visible: " + new Date().getTime());
-      },
-      
-      /**
-       * Handle key input to rotate and move the player
-       */
-      handleInput: function handleInput(input)
-      {
-         var h = this.heading % 360;
-         
-         // TODO: hack, fix this to maintain +ve heading or change calculation below...
-         if (h < 0) h += 360;
-         
-         // first section tweens the current rendered heading of the player towards
-         // the desired heading - but the next section actually applies a vector
-         // TODO: this seems over complicated - must be an easier way to do this...
-         if (input.left)
-         {
-            if (h > 270 || h < 90)
-            {
-               if (h > 270) this.heading -= ((h - 270) * 0.2);
-               else this.heading -= ((h + 90) * 0.2);
-            }
-            else this.heading += ((270 - h) * 0.2);
-         }
-         if (input.right)
-         {
-            if (h < 90 || h > 270)
-            {
-               if (h < 90) this.heading += ((90 - h) * 0.2);
-               else this.heading += ((h - 90) * 0.2);
-            }
-            else this.heading -= ((h - 90) * 0.2);
-         }
-         if (input.up)
-         {
-            if (h < 180)
-            {
-               this.heading -= (h * 0.2);
-            }
-            else this.heading += ((360 - h) * 0.2);
-         }
-         if (input.down)
-         {
-            if (h < 180)
-            {
-               this.heading += ((180 - h) * 0.2);
-            }
-            else this.heading -= ((h - 180) * 0.2);
-         }
-         
-         // second section applies the direct thrust angled vector
-         // this ensures a snappy control method with the above heading effect
-         var angle = null;
-         if (input.left)
-         {
-            if (input.up) angle = 315;
-            else if (input.down) angle = 225;
-            else angle = 270;
-         }
-         else if (input.right)
-         {
-            if (input.up) angle = 45;
-            else if (input.down) angle = 135;
-            else angle = 90;
-         }
-         else if (input.up)
-         {
-            if (input.left) angle = 315;
-            else if (input.right) angle = 45;
-            else angle = 0;
-         }
-         else if (input.down)
-         {
-            if (input.left) angle = 225;
-            else if (input.right) angle = 135;
-            else angle = 180;
-         }
-         if (angle !== null)
-         {
-            this.thrust(angle);
-         }
-         else
-         {
-            // reduce thrust over time if player isn't actively moving
-            this.vector.scale(0.9);
-         }
-      },
-      
-      /**
-       * Execute player forward thrust request
-       * Automatically a delay is used between each application - to ensure stable thrust on all machines.
-       */
-      thrust: function thrust(angle)
-      {
-         // now test we did not thrust too recently - to stop fast key repeat issues
-         if (GameHandler.frameCount - this.thrustRecharge > this.THRUST_DELAY)
-         {
-            // update last frame count
-            this.thrustRecharge = GameHandler.frameCount;
-            
-            // generate a small thrust vector
-            var t = new Vector(0.0, -2.00);
-            
-            // rotate thrust vector by player current heading
-            t.rotate(angle * RAD);
-            
-            // add player thrust vector to position
-            this.vector.add(t);
-            
-            // player can't exceed maximum velocity - scale vector down if
-            // this occurs - do this rather than not adding the thrust at all
-            // otherwise the player cannot turn and thrust at max velocity
-            if (this.vector.length() > this.MAX_PLAYER_VELOCITY)
-            {
-               this.vector.scale(this.MAX_PLAYER_VELOCITY / this.vector.length());
-            }
-         }
-         // mark so that we know to render engine thrust graphics
-         this.engineThrust = true;
-      },
-      
-      damageBy: function damageBy(enemy)
-      {
-         this.energy -= enemy.playerDamage;
-         if (this.energy <= 0)
-         {
-            this.energy = 0;
-            this.kill();
-         }
-      },
-      
-      kill: function kill()
-      {
-         this.alive = false;
-         this.killedOnFrame = GameHandler.frameCount;
-      },
-      
-      /**
-       * Fire primary weapon(s)
-       * @param bulletList {Array} to add bullet(s) to on success
-       * @param heading {Number} bullet heading
-       */
-      firePrimary: function firePrimary(bulletList, vector, heading)
-      {
-         // attempt to fire the primary weapon(s)
-         // first ensure player is alive
-         if (this.alive)
-         {
-            for (var w in this.primaryWeapons)
-            {
-               var b = this.primaryWeapons[w].fire(vector, heading);
-               if (b)
-               {
-                  for (var i=0; i<b.length; i++)
-                  {
-                     bulletList.push(b[i]);
-                  }
-               }
-            }
-         }
-      },
-      
-      reset: function reset(persistPowerUps)
-      {
-         // reset energy, alive status, weapons and power up flags
-         this.alive = true;
-         if (!persistPowerUps)
-         {
-            // reset weapons
-            this.primaryWeapons = [];
-            this.primaryWeapons["main"] = new Arena.PrimaryWeapon(this);
-            
-            // reset powerup settings
-            this.bounceWeapons = false;
-         }
-         this.energy = this.ENERGY_INIT;
-      }
-   });
-})();
-
-
-/**
- * Player score multiplier collectable class.
- *
- * @namespace Arena
- * @class Arena.Multiplier
- */
-(function()
-{
-   Arena.Multiplier = function(p, v, h)
-   {
-      Arena.Multiplier.superclass.constructor.call(this, p, v, this.LIFESPAN);
-      this.radius = 10;
-      this.rotation = 0;
-      return this;
-   };
-   
-   extend(Arena.Multiplier, Game.EffectActor,
-   {
-      LIFESPAN: 250,
-      FADE_LENGTH: 16,
-      rotation: 0,
-      
-      /**
-       * Multiplier collectable rendering method
-       * 
-       * @param ctx {object} Canvas rendering context
-       * @param world {object} World metadata
-       */
-      onRender: function onRender(ctx, world)
-      {
-         // transform world to screen - non-visible returns null
-         var viewposition = Game.worldToScreen(this.position, world, this.radius);
-         if (viewposition)
-         {
-            var r = this.radius * 0.6;
-            ctx.save();
-            ctx.globalCompositeOperation = "lighter";
-            if (this.lifespan < this.FADE_LENGTH)
-            {
-               ctx.globalAlpha = (1.0 / this.FADE_LENGTH) * this.lifespan;
-            }
-            ctx.strokeStyle = ctx.shadowColor = "rgb(255,180,0)";
-            ctx.translate(viewposition.x, viewposition.y);
-            ctx.scale(world.scale, world.scale);
-            ctx.rotate(this.rotation);
-            ctx.strokeRect(-r, -r, this.radius*1.2, this.radius*1.2);
-            ctx.restore();
-            this.rotation += 0.02;
-         }
-      },
-      
-      collected: function collected(game, player, scene)
-      {
-         if (++game.scoreMultiplier % 10 === 0)
-         {
-            // display multiplier to player every large increment
-            var vec = new Vector(0, -5.0).add(this.vector);
-            scene.effects.push(new Arena.TextIndicator(
-               this.position.clone(), vec, "x" + game.scoreMultiplier, 32, "white", 32));
-         }
-      }
-   });
-})();
-
-
-/**
- * Player energy boost powerup collectable class.
- *
- * @namespace Arena
- * @class Arena.EnergyBoostPowerup
- */
-(function()
-{
-   Arena.EnergyBoostPowerup = function(p, v, h)
-   {
-      Arena.EnergyBoostPowerup.superclass.constructor.call(this, p, v, this.LIFESPAN);
-      this.radius = 12;
-      this.rotation = 0;
-      return this;
-   };
-   
-   extend(Arena.EnergyBoostPowerup, Game.EffectActor,
-   {
-      LIFESPAN: 350,
-      FADE_LENGTH: 16,
-      rotation: 0,
-      
-      /**
-       * EnergyBoostPowerup collectable rendering method
-       * 
-       * @param ctx {object} Canvas rendering context
-       * @param world {object} World metadata
-       */
-      onRender: function onRender(ctx, world)
-      {
-         // transform world to screen - non-visible returns null
-         var viewposition = Game.worldToScreen(this.position, world, this.radius);
-         if (viewposition)
-         {
-            var r = this.radius * 0.6;
-            ctx.save();
-            ctx.globalCompositeOperation = "lighter";
-            if (this.lifespan < this.FADE_LENGTH)
-            {
-               ctx.globalAlpha = (1.0 / this.FADE_LENGTH) * this.lifespan;
-            }
-            ctx.lineWidth = 2.0;
-            ctx.strokeStyle = ctx.shadowColor = "rgb(100,255,0)";
-            ctx.translate(viewposition.x, viewposition.y);
-            ctx.scale(world.scale, world.scale);
-            ctx.rotate(this.rotation);
-            ctx.strokeRect(-r, -r, this.radius*1.2, this.radius*1.2);
-            ctx.restore();
-            this.rotation += 0.05;
-         }
-      },
-      
-      collected: function collected(game, player, scene)
-      {
-         // increment player energy
-         player.energy += 25;
-         if (player.energy > player.ENERGY_INIT)
-         {
-            player.energy = player.ENERGY_INIT;
-         }
-         
-         // display indicator
-         var vec = new Vector(0, -5.0).add(this.vector);
-         scene.effects.push(new Arena.TextIndicator(
-            this.position.clone(), vec, "Energy Boost!", 32, "white", 32));
-      }
-   });
-})();
-/**
- * Weapon system base class for the player actor.
- * 
- * @namespace Arena
- * @class Arena.Weapon
- */
-(function()
-{
-   Arena.Weapon = function(player)
-   {
-      this.player = player;
-      return this;
-   };
-   
-   Arena.Weapon.prototype =
-   {
-      rechargeTime: 3,
-      weaponRecharged: 0,
-      player: null,
-      
-      fire: function(v, h)
-      {
-         // now test we did not fire too recently
-         if (GameHandler.frameCount - this.weaponRecharged > this.rechargeTime)
-         {
-            // ok, update last fired frame and we can now generate a bullet
-            this.weaponRecharged = GameHandler.frameCount;
-            
-            return this.doFire(v, h);
-         }
-      },
-      
-      doFire: function(v, h)
-      {
-      }
-   };
-})();
-
-
-/**
- * Basic primary weapon for the player actor.
- * 
- * @namespace Arena
- * @class Arena.PrimaryWeapon
- */
-(function()
-{
-   Arena.PrimaryWeapon = function(player)
-   {
-      Arena.PrimaryWeapon.superclass.constructor.call(this, player);
-      this.rechargeTime = this.DEFAULT_RECHARGE;
-      return this;
-   };
-   
-   extend(Arena.PrimaryWeapon, Arena.Weapon,
-   {
-      DEFAULT_RECHARGE: 5,
-      bulletCount: 1,   // increase this to output more intense bullet stream
-      
-      doFire: function(vector, heading)
-      {
-         var bullets = [],
-             count = this.bulletCount,
-             total = (count > 2 ? randomInt(count - 1, count) : count);
-         for (var i=0; i<total; i++)
-         {
-            // slightly randomize the spread based on bullet count
-            var offset = (count > 1 ? Rnd() * PIO16 * (count-1) : 0),
-                h = heading + offset - (PIO32 * (count-1)),
-                v = vector.nrotate(offset - (PIO32 * (count-1))).scale(1 + Rnd() * 0.1 - 0.05);
-            v.add(this.player.vector);
-            
-            bullets.push(new Arena.Bullet(this.player.position.clone(), v, h));
-         }
-         return bullets;
-      }
-   });
-})();
-
-
-/**
- * Player Bullet actor class.
- *
- * @namespace Arena
- * @class Arena.Bullet
- */
-(function()
-{
-   Arena.Bullet = function(p, v, h, lifespan)
-   {
-      Arena.Bullet.superclass.constructor.call(this, p, v);
-      this.heading = h;
-      this.lifespan = (lifespan ? lifespan : this.BULLET_LIFESPAN);
-      this.radius = this.BULLET_RADIUS;
-      return this;
-   };
-   
-   extend(Arena.Bullet, Game.Actor,
-   {
-      BULLET_RADIUS: 12,
-      BULLET_LIFESPAN: 30,
-      FADE_LENGTH: 5,
-      
-      /**
-       * Bullet heading
-       */
-      heading: 0,
-      
-      /**
-       * Bullet lifespan remaining
-       */
-      lifespan: 0,
-      
-      /**
-       * Bullet power energy
-       */
-      powerLevel: 1,
-      
-      /**
-       * Bullet rendering method
-       * 
-       * @param ctx {object} Canvas rendering context
-       * @param world {object} World metadata
-       */
-      onRender: function onRender(ctx, world)
-      {
-         ctx.save();
-         ctx.shadowBlur = 0;
-         ctx.globalCompositeOperation = "lighter";
-         if (this.worldToScreen(ctx, world, this.BULLET_RADIUS) &&
-             this.lifespan < this.BULLET_LIFESPAN - 1)   // hack - to stop draw over player ship
-         {
-            if (this.lifespan < this.FADE_LENGTH)
-            {
-               ctx.globalAlpha = (1.0 / this.FADE_LENGTH) * this.lifespan;
-            }
-            
-            // rotate into the correct heading
-            ctx.rotate(this.heading * RAD);
-            
-            // draw bullet primary weapon
-            try
-            {
-               ctx.drawImage(GameHandler.prerenderer.images["playerweapon"][0], -20, -20);
-            }
-            catch (error)
-            {
-               if (console !== undefined) console.log(error.message);
-            }
-         }
-         ctx.restore();
-      },
-      
-      /**
-       * Actor expiration test
-       * 
-       * @return true if expired and to be removed from the actor list, false if still in play
-       */
-      expired: function expired()
-      {
-         // deduct lifespan from the bullet
-         return (--this.lifespan === 0);
-      },
-      
-      /**
-       * Area effect weapon radius - zero for primary bullets
-       */
-      effectRadius: function effectRadius()
-      {
-         return 0;
-      },
-      
-      power: function power()
-      {
-         return this.powerLevel;
-      }
-   });
-})();
-
-
-/**
- * Enemy Bullet actor class.
- *
- * @namespace Arena
- * @class Arena.EnemyBullet
- */
-(function()
-{
-   Arena.EnemyBullet = function(p, v, power)
-   {
-      Arena.EnemyBullet.superclass.constructor.call(this, p, v);
-      this.powerLevel = this.playerDamage = power;
-      this.lifespan = this.BULLET_LIFESPAN;
-      this.radius = this.BULLET_RADIUS;
-      return this;
-   };
-   
-   extend(Arena.EnemyBullet, Game.Actor,
-   {
-      BULLET_LIFESPAN: 75,
-      BULLET_RADIUS: 10,
-      FADE_LENGTH: 8,
-      powerLevel: 0,
-      playerDamage: 0,
-      
-      /**
-       * Bullet lifespan remaining
-       */
-      lifespan: 0,
-      
-      /**
-       * Bullet rendering method
-       * 
-       * @param ctx {object} Canvas rendering context
-       * @param world {object} World metadata
-       */
-      onRender: function onRender(ctx, world)
-      {
-         ctx.save();
-         ctx.globalCompositeOperation = "lighter";
-         if (this.worldToScreen(ctx, world, this.BULLET_RADIUS) &&
-             this.lifespan < this.BULLET_LIFESPAN - 1)   // hack - to stop draw over enemy
-         {
-            if (this.lifespan < this.FADE_LENGTH)
-            {
-               ctx.globalAlpha = (1.0 / this.FADE_LENGTH) * this.lifespan;
-            }
-            ctx.shadowColor = ctx.fillStyle = "rgb(150,255,150)";
-            
-            var rad = this.BULLET_RADIUS - 2;
-            ctx.beginPath();
-            ctx.arc(0, 0, (rad-1 > 0 ? rad-1 : 0.1), 0, TWOPI, true);
-            ctx.closePath();
-            ctx.fill();
-            
-            ctx.rotate((GameHandler.frameCount % 1800) / 5);
-            ctx.beginPath()
-            ctx.moveTo(rad * 2, 0);
-            for (var i=0; i<7; i++)
-            {
-               ctx.rotate(PIO4);
-               if (i%2 === 0)
-               {
-                  ctx.lineTo((rad * 2 / 0.5) * 0.2, 0);
-               }
-               else
-               {
-                  ctx.lineTo(rad * 2, 0);
-               }
-            }
-            ctx.closePath();
-            ctx.fill();
-         }
-         ctx.restore();
-      },
-      
-      /**
-       * Actor expiration test
-       * 
-       * @return true if expired and to be removed from the actor list, false if still in play
-       */
-      expired: function expired()
-      {
-         // deduct lifespan from the bullet
-         return (--this.lifespan === 0);
-      },
-      
-      power: function power()
-      {
-         return this.powerLevel;
-      }
-   });
-})();
-/**
- * Enemy Ship actor class.
- * 
- * @namespace Arena
- * @class Arena.EnemyShip
- */
-(function()
-{
-   Arena.EnemyShip = function(scene, type)
-   {
-      // enemy score multiplier based on type buy default - but some enemies
-      // will tweak this in the individual setup code later
-      this.type = this.scoretype = type;
-      
-      // generate enemy at start position - not too close to the player
-      var p, v = null;
-      while (!v)
-      {
-         p = new Vector(Rnd() * scene.world.size, Rnd() * scene.world.size);
-         if (scene.player.position.distance(p) > 220)
-         {
-            v = new Vector(0,0);
-         }
-      }
-      Arena.EnemyShip.superclass.constructor.call(this, p, v);
-      
-      // 3D sprite object - must be created after constructor call
-      var me = this;
-      var obj = new K3D.K3DObject();
-      with (obj)
-      {
-         drawmode = "wireframe";
-         shademode = "depthcue";
-         depthscale = 32;
-         linescale = 3;
-         perslevel = 256;
-         
-         switch (type)
-         {
-            case 0:
-               // Dumbo: blue stretched cubiod
-               me.radius = 22;
-               me.playerDamage = 10;
-               me.colorRGB = "rgb(0,128,255)";
-               color = [0,128,255];
-               addphi = -1.0; addgamma = -0.75;
-               init(
-                  [{x:-20,y:-20,z:12}, {x:-20,y:20,z:12}, {x:20,y:20,z:12}, {x:20,y:-20,z:12}, {x:-10,y:-10,z:-12}, {x:-10,y:10,z:-12}, {x:10,y:10,z:-12}, {x:10,y:-10,z:-12}],
-                  [{a:0,b:1}, {a:1,b:2}, {a:2,b:3}, {a:3,b:0}, {a:4,b:5}, {a:5,b:6}, {a:6,b:7}, {a:7,b:4}, {a:0,b:4}, {a:1,b:5}, {a:2,b:6}, {a:3,b:7}],
-                  []);
-               break;
-            
-            case 1:
-               // Zoner: yellow diamond
-               me.radius = 22;
-               me.playerDamage = 10;
-               me.colorRGB = "rgb(255,255,0)";
-               color = [255,255,0];
-               addphi = 0.5; addgamma = -0.5; addtheta = -1.0;
-               init(
-                  [{x:-20,y:-20,z:0}, {x:-20,y:20,z:0}, {x:20,y:20,z:0}, {x:20,y:-20,z:0}, {x:0,y:0,z:-20}, {x:0,y:0,z:20}],
-                  [{a:0,b:1}, {a:1,b:2}, {a:2,b:3}, {a:3,b:0}, {a:0,b:4}, {a:1,b:4}, {a:2,b:4}, {a:3,b:4}, {a:0,b:5}, {a:1,b:5}, {a:2,b:5}, {a:3,b:5}],
-                  []);
-               break;
-            
-            case 2:
-               // Tracker: red flattened square
-               me.radius = 22;
-               me.health = 2;
-               me.playerDamage = 15;
-               me.colorRGB = "rgb(255,96,0)";
-               color = [255,96,0];
-               addgamma = 1.0;
-               init(
-                  [{x:-20,y:-20,z:5}, {x:-20,y:20,z:5}, {x:20,y:20,z:5}, {x:20,y:-20,z:5}, {x:-15,y:-15,z:-5}, {x:-15,y:15,z:-5}, {x:15,y:15,z:-5}, {x:15,y:-15,z:-5}],
-                  [{a:0,b:1}, {a:1,b:2}, {a:2,b:3}, {a:3,b:0}, {a:4,b:5}, {a:5,b:6}, {a:6,b:7}, {a:7,b:4}, {a:0,b:4}, {a:1,b:5}, {a:2,b:6}, {a:3,b:7}],
-                  []);
-               break;
-            
-            case 3:
-               // Borg: big green cube
-               me.radius = 52;
-               me.health = 5;
-               me.playerDamage = 25;
-               me.colorRGB = "rgb(0,255,64)";
-               color = [0,255,64];
-               depthscale = 96;  // tweak for larger object
-               addphi = -1.5;
-               init(
-                  [{x:-40,y:-40,z:40}, {x:-40,y:40,z:40}, {x:40,y:40,z:40}, {x:40,y:-40,z:40}, {x:-40,y:-40,z:-40}, {x:-40,y:40,z:-40}, {x:40,y:40,z:-40}, {x:40,y:-40,z:-40}],
-                  [{a:0,b:1}, {a:1,b:2}, {a:2,b:3}, {a:3,b:0}, {a:4,b:5}, {a:5,b:6}, {a:6,b:7}, {a:7,b:4}, {a:0,b:4}, {a:1,b:5}, {a:2,b:6}, {a:3,b:7}],
-                  []);
-               break;
-            
-            case 4:
-               // Dodger: small cyan cube
-               me.radius = 25;
-               me.playerDamage = 10;
-               me.colorRGB = "rgb(0,255,255)";
-               color = [0,255,255];
-               addphi = 0.5; addtheta = -3.0;
-               init(
-                  [{x:-20,y:-20,z:20}, {x:-20,y:20,z:20}, {x:20,y:20,z:20}, {x:20,y:-20,z:20}, {x:-20,y:-20,z:-20}, {x:-20,y:20,z:-20}, {x:20,y:20,z:-20}, {x:20,y:-20,z:-20}],
-                  [{a:0,b:1}, {a:1,b:2}, {a:2,b:3}, {a:3,b:0}, {a:4,b:5}, {a:5,b:6}, {a:6,b:7}, {a:7,b:4}, {a:0,b:4}, {a:1,b:5}, {a:2,b:6}, {a:3,b:7}],
-                  []);
-               break;
-            
-            case 5:
-               // Splitter: medium purple pyrimid (converts to 2x smaller versions when hit)
-               me.radius = 25;
-               me.health = 3;
-               me.playerDamage = 20;
-               me.colorRGB = "rgb(148,0,255)";
-               color = [148,0,255];
-               depthscale = 56;  // tweak for larger object
-               addphi = 3.0;
-               init(
-                  [{x:-30,y:-20,z:0}, {x:0,y:-20,z:30}, {x:30,y:-20,z:0}, {x:0,y:-20,z:-30}, {x:0,y:30,z:0}],
-                  [{a:0,b:1}, {a:1,b:2}, {a:2,b:3}, {a:3,b:0}, {a:0,b:4}, {a:1,b:4}, {a:2,b:4}, {a:3,b:4}],
-                  []);
-               break;
-            
-            case 6:
-               // Bomber: medium magenta star - dodge bullets, dodge player!
-               me.radius = 28;
-               me.health = 5;
-               me.playerDamage = 20;
-               me.colorRGB = "rgb(255,0,255)";
-               color = [255,0,255];
-               depthscale = 56;  // tweak for larger object
-               addgamma = -5.0;
-               init(
-                  [{x:-30,y:-30,z:10}, {x:-30,y:30,z:10}, {x:30,y:30,z:10}, {x:30,y:-30,z:10}, {x:-15,y:-15,z:-15}, {x:-15,y:15,z:-15}, {x:15,y:15,z:-15}, {x:15,y:-15,z:-15}],
-                  [{a:0,b:1}, {a:1,b:2}, {a:2,b:3}, {a:3,b:0}, {a:4,b:5}, {a:5,b:6}, {a:6,b:7}, {a:7,b:4}, {a:0,b:4}, {a:1,b:5}, {a:2,b:6}, {a:3,b:7}],
-                  []);
-               break;
-            
-            case 99:
-               // Splitter-mini: see Splitter above
-               me.scoretype = 4;    // override default score type setting
-               me.dropsMutliplier = false;
-               me.radius = 12;
-               me.health = 1;
-               me.playerDamage = 5;
-               me.colorRGB = "rgb(148,0,211)";
-               color = [148,0,211];
-               depthscale = 16;  // tweak for smaller object
-               addphi = 5.0;
-               init(
-                  [{x:-15,y:-10,z:0}, {x:0,y:-10,z:15}, {x:15,y:-10,z:0}, {x:0,y:-10,z:-15}, {x:0,y:15,z:0}],
-                  [{a:0,b:1}, {a:1,b:2}, {a:2,b:3}, {a:3,b:0}, {a:0,b:4}, {a:1,b:4}, {a:2,b:4}, {a:3,b:4}],
-                  []);
-               break;
-         }
-      }
-      this.setK3DObject(obj);
-      
-      return this;
-   };
-   
-   extend(Arena.EnemyShip, Arena.K3DActor,
-   {
-      BULLET_RECHARGE: 50,
-      SPAWN_LENGTH: 20, // TODO: replace this with anim state machine
-      aliveTime: 0,     // TODO: replace this with anim state machine
-      type: 0,
-      scoretype: 0,
-      dropsMutliplier: true,
-      health: 1,
-      colorRGB: null,
-      playerDamage: 0,
-      bulletRecharge: 0,
-      hit: false, // TODO: replace with state? - "extends" default render state...?
-      
-      onUpdate: function onUpdate(scene)
-      {
-         // TODO: replace this with anim state machine
-         if (++this.aliveTime < this.SPAWN_LENGTH)
-         {
-            // TODO: needs enemy state implemented so can test for "alive" state
-            //       for collision detection?
-            //       other methods can then test state such as onRender()
-            //       SPAWNED->ALIVE->DEAD
-            return;
-         }
-         else if (this.aliveTime === this.SPAWN_LENGTH)
-         {
-            // initial vector needed for some enemy types - others will set later
-            this.vector = new Vector(4 * (Rnd < 0.5 ? 1 : -1), 4 * (Rnd < 0.5 ? 1 : -1));
-         }
-         switch (this.type)
-         {
-            case 0:
-               // dumb - change direction randomly
-               if (Rnd() < 0.01)
-               {
-                  this.vector.y = -(this.vector.y + (0.5 - Rnd()));
-               }
-               break;
-            
-            case 1:
-               // randomly reorientate towards player ("perception level")
-               // so player can avade by moving around them
-               if (Rnd() < 0.04)
-               {
-                  // head towards player - generate a vector pointed at the player
-                  // by calculating a vector between the player and enemy positions
-                  var v = scene.player.position.nsub(this.position);
-                  // scale resulting vector down to fixed vector size i.e. speed
-                  this.vector = v.scaleTo(4);
-               }
-               break;
-            
-            case 2:
-               // very perceptive and faster - this one is mean
-               if (Rnd() < 0.2)
-               {
-                  var v = scene.player.position.nsub(this.position);
-                  this.vector = v.scaleTo(8);
-               }
-               break;
-            
-            case 3:
-               // fast dash towards player, otherwise it slows down
-               if (Rnd() < 0.03)
-               {
-                  var v = scene.player.position.nsub(this.position);
-                  this.vector = v.scaleTo(12);
-               }
-               else
-               {
-                  this.vector.scale(0.95);
-               }
-               break;
-            
-            case 4:
-               // perceptive and fast - and tries to dodgy bullets!
-               var dodged = false;
-               
-               // if we are close to the player then don't try and dodge,
-               // otherwise enemy might dash away rather than go for the kill
-               if (scene.player.position.nsub(this.position).length() > 150)
-               {
-                  var p = this.position,
-                      r = this.radius + 50;  // bullet "distance" perception
-                  
-                  // look at player bullets list - are any about to hit?
-                  for (var i=0, j=scene.playerBullets.length, bullet, n; i < j; i++)
-                  {
-                     bullet = scene.playerBullets[i];
-                     
-                     // test the distance against the two radius combined
-                     if (bullet.position.distance(p) <= bullet.radius + r)
-                     {
-                        // if so attempt a fast sideways dodge!
-                        var v = bullet.position.nsub(p).scaleTo(12);
-                        // randomise dodge direction a bit
-                        v.rotate((n = Rnd()) < 0.5 ? n*PIO4 : -n*PIO4);
-                        v.invert();
-                        this.vector = v;
-                        dodged = true;
-                        break;
-                     }
-                  }
-               }
-               if (!dodged && Rnd() < 0.04)
-               {
-                  var v = scene.player.position.nsub(this.position);
-                  this.vector = v.scaleTo(8);
-               }
-               break;
-            
-            case 5:
-               if (Rnd() < 0.04)
-               {
-                  var v = scene.player.position.nsub(this.position);
-                  this.vector = v.scaleTo(5);
-               }
-               break;
-            
-            case 6:
-               // if we are near the player move away
-               // if we are far from the player move towards
-               var v = scene.player.position.nsub(this.position);
-               if (v.length() > 400)
-               {
-                  // move closer
-                  if (Rnd() < 0.08) this.vector = v.scaleTo(8);
-               }
-               else if (v.length() < 350)
-               {
-                  // move away
-                  if (Rnd() < 0.08) this.vector = v.invert().scaleTo(8);
-               }
-               else
-               {
-                  // slow down into a firing position
-                  this.vector.scale(0.8);
-                  
-                  // reguarly fire at the player
-                  if (GameHandler.frameCount - this.bulletRecharge > this.BULLET_RECHARGE && scene.player.alive)
-                  {
-                     // update last fired frame and generate a bullet
-                     this.bulletRecharge = GameHandler.frameCount;
-                     
-                     // generate a vector pointed at the player
-                     // by calculating a vector between the player and enemy positions
-                     // then scale to a fixed size - i.e. bullet speed
-                     var v = scene.player.position.nsub(this.position).scaleTo(10);
-                     // slightly randomize the direction to apply some accuracy issues
-                     v.x += (Rnd() * 2 - 1);
-                     v.y += (Rnd() * 2 - 1);
-                     
-                     var bullet = new Arena.EnemyBullet(this.position.clone(), v, 10);
-                     scene.enemyBullets.push(bullet);
-                  }
-               }
-               break;
-            
-            case 99: 
-               if (Rnd() < 0.04)
-               {
-                  var v = scene.player.position.nsub(this.position);
-                  this.vector = v.scaleTo(8);
-               }
-               break;
-         }
-      },
-      
-      /**
-       * Enemy rendering method
-       * 
-       * @param ctx {object} Canvas rendering context
-       * @param world {object} World metadata
-       */
-      onRender: function onRender(ctx, world)
-      {
-         ctx.save();
-         if (this.worldToScreen(ctx, world, this.radius))
-         {
-            // render 3D sprite
-            if (!this.hit)
-            {
-               ctx.shadowColor = this.colorRGB;
-            }
-            else
-            {
-               // override colour with plain white for "hit" effect
-               ctx.shadowColor = "white";
-               var oldColor = this.k3dObject.color;
-               this.k3dObject.color = [255,255,255];
-               this.k3dObject.shademode = "plain";
-            }
-            // TODO: replace this with anim state machine test...
-            // TODO: adjust RADIUS for collision etc. during spawn!
-            if (this.aliveTime < this.SPAWN_LENGTH)
-            {
-               // nifty scaling effect as an enemy spawns into position
-               var scale = 1 - (this.SPAWN_LENGTH - this.aliveTime) / this.SPAWN_LENGTH;
-               if (scale <= 0) scale = 0.01;
-               else if (scale > 1) scale = 1;
-               ctx.scale(scale, scale);
-            }
-            this.renderK3D(ctx);
-            if (this.hit)
-            {
-               // restore colour and depthcue rendering mode
-               this.k3dObject.color = oldColor;
-               this.k3dObject.shademode = "depthcue";
-               this.hit = false;
-            }
-         }
-         ctx.restore();
-      },
-      
-      damageBy: function damageBy(force)
-      {
-         // record hit - will change enemy colour for a single frame
-         this.hit = true;
-         if (force === -1 || (this.health -= force) <= 0)
-         {
-            this.alive = false;
-         }
-         return !this.alive;
-      },
-      
-      onDestroyed: function onDestroyed(scene, player)
-      {
-         if (this.type === 5)
-         {
-            // Splitter enemy divides into two smaller ones
-            var enemy = new Arena.EnemyShip(scene, 99);
-            // update position and vector
-            // TODO: move this as option in constructor
-            enemy.vector = this.vector.nrotate(PIO2);
-            enemy.position = this.position.nadd(enemy.vector);
-            scene.enemies.push(enemy);
-            
-            enemy = new Arena.EnemyShip(scene, 99);
-            enemy.vector = this.vector.nrotate(-PIO2);
-            enemy.position = this.position.nadd(enemy.vector);
-            scene.enemies.push(enemy);
-         }
-      }
-   });
-})();
-/**
- * Particle emitter effect actor class.
- * 
- * A simple particle emitter, that does not recycle particles, but sets itself as expired() once
- * all child particles have expired.
- * 
- * Requires a function known as the emitter that is called per particle generated.
- * 
- * @namespace Arena
- * @class Arena.Particles
- */
-(function()
-{
-   /**
-    * Constructor
-    * 
-    * @param p {Vector} Emitter position
-    * @param v {Vector} Emitter velocity
-    * @param count {Integer} Number of particles
-    * @param fnEmitter {Function} Emitter function to call per particle generated
-    */
-   Arena.Particles = function(p, v, count, fnEmitter)
-   {
-      Arena.Particles.superclass.constructor.call(this, p, v);
-      
-      // generate particles based on the supplied emitter function
-      this.particles = new Array(count);
-      for (var i=0; i<count; i++)
-      {
-         this.particles[i] = fnEmitter.call(this, i);
-      }
-      
-      return this;
-   };
-   
-   extend(Arena.Particles, Game.Actor,
-   {
-      particles: null,
-      
-      /**
-       * Particle effect rendering method
-       * 
-       * @param ctx {object} Canvas rendering context
-       */
-      onRender: function onRender(ctx, world)
-      {
-         ctx.save();
-         ctx.shadowBlur = 0;
-         ctx.globalCompositeOperation = "lighter";
-         for (var i=0, particle, viewposition; i<this.particles.length; i++)
-         {
-            particle = this.particles[i];
-            
-            // update particle and test for lifespan
-            if (particle.update())
-            {
-               viewposition = Game.worldToScreen(particle.position, world, particle.size);
-               if (viewposition)
-               {
-                  ctx.save();
-                  ctx.translate(viewposition.x, viewposition.y);
-                  ctx.scale(world.scale, world.scale);
-                  particle.render(ctx);
-                  ctx.restore();
-               }
-            }
-            else
-            {
-               // particle no longer alive, remove from list
-               this.particles.splice(i, 1);
-            }
-         }
-         ctx.restore();
-      },
-      
-      expired: function expired()
-      {
-         return (this.particles.length === 0);
-      }
-   });
-})();
-
-
-/**
- * Default Arena Particle structure.
- * Currently supports three particle types; dot, line and smudge.
- */
-function ArenaParticle(position, vector, size, type, lifespan, fadelength, colour)
-{
-   this.position = position;
-   this.vector = vector;
-   this.size = size;
-   this.type = type;
-   this.lifespan = lifespan;
-   this.fadelength = fadelength;
-   this.colour = colour ? colour : "rgb(255,125,50)"; // default colour if none set
-   // randomize rotation speed and angle for line particle
-   if (type === 1)
-   {
-      this.rotate = Rnd() * TWOPI;
-      this.rotationv = Rnd() - 0.5;
-   }
-   
-   this.update = function()
-   {
-      this.position.add(this.vector);
-      return (--this.lifespan !== 0);
-   };
-   
-   this.render = function(ctx)
-   {
-  	   // NOTE: the try/catch here is to handle where FireFox gets
-  	   //       upset when rendering images outside the canvas area
-      try
-	  	{
-         ctx.globalAlpha = (this.lifespan < this.fadelength ? ((1 / this.fadelength) * this.lifespan) : 1);
-         switch (this.type)
-         {
-            case 0:  // point (prerendered image)
-               // prerendered images for each enemy colour with health > 1
-               // lookup based on particle colour e.g. points_rgb(x,y,z)
-               ctx.drawImage(
-                  GameHandler.prerenderer.images["points_" + this.colour][this.size], 0, 0);
-               break;
-            case 1:  // line
-               var s = this.size;
-               ctx.rotate(this.rotate);
-               this.rotate += this.rotationv;
-               // specific line colour - for enemy explosion pieces
-               ctx.strokeStyle = this.colour;
-               ctx.lineWidth = 2.0;
-               ctx.beginPath();
-               ctx.moveTo(-s, -s);
-               ctx.lineTo(s, s);
-               ctx.closePath();
-               ctx.stroke();
-               break;
-            case 2:  // smudge (prerendered image)
-      		  	ctx.drawImage(GameHandler.prerenderer.images["smudges"][this.size - 4], 0, 0);
-      		  	break;
-         }
-      }
-      catch (error)
-      {
-         if (console !== undefined) console.log(error.message);
-      }
-   };
-}
-
-
-/**
- * Enemy explosion - Particle effect actor class.
- * 
- * @namespace Arena
- * @class Arena.EnemyExplosion
- */
-(function()
-{
-   /**
-    * Constructor
-    */
-   Arena.EnemyExplosion = function(p, v, enemy)
-   {
-      Arena.EnemyExplosion.superclass.constructor.call(this, p, v, 16, function()
-         {
-            // randomise start position slightly
-            var pos = p.clone();
-            pos.x += randomInt(-5, 5);
-            pos.y += randomInt(-5, 5);
-            // randomise radial direction vector - speed and angle, then add parent vector
-            switch (randomInt(0, 2))
-            {
-               case 0:
-                  var t = new Vector(0, randomInt(20, 25));
-                  t.rotate(Rnd() * TWOPI);
-                  t.add(v);
-                  return new ArenaParticle(
-                     pos, t, ~~(Rnd() * 4), 0, 20, 15);
-               case 1:
-                  var t = new Vector(0, randomInt(5, 10));
-                  t.rotate(Rnd() * TWOPI);
-                  t.add(v);
-                  // create line particle - size based on enemy type
-                  return new ArenaParticle(
-                     pos, t, (enemy.type !== 3 ? Rnd() * 5 + 5 : Rnd() * 10 + 10), 1, 20, 15, enemy.colorRGB);
-               case 2:
-                  var t = new Vector(0, randomInt(2, 4));
-                  t.rotate(Rnd() * TWOPI);
-                  t.add(v);
-                  return new ArenaParticle(
-                     pos, t, ~~(Rnd() * 4 + 4), 2, 20, 15);
-            }
-         });
-      
-      return this;
-   };
-   
-   extend(Arena.EnemyExplosion, Arena.Particles);
-})();
-
-
-/**
- * Enemy impact effect - Particle effect actor class.
- * Used when an enemy is hit by player bullet but not destroyed.
- * 
- * @namespace Arena
- * @class Arena.EnemyImpact
- */
-(function()
-{
-   /**
-    * Constructor
-    */
-   Arena.EnemyImpact = function(p, v, enemy)
-   {
-      Arena.EnemyImpact.superclass.constructor.call(this, p, v, 5, function()
-         {
-            // slightly randomise vector angle - then add parent vector
-            var t = new Vector(0, Rnd() < 0.5 ? randomInt(-5, -10) : randomInt(5, 10));
-            t.rotate(Rnd() * PIO2 - PIO4);
-            t.add(v);
-            return new ArenaParticle(
-               p.clone(), t, ~~(Rnd() * 4), 0, 15, 10, enemy.colorRGB);
-         });
-      
-      return this;
-   };
-   
-   extend(Arena.EnemyImpact, Arena.Particles);
-})();
-
-
-/**
- * Bullet impact effect - Particle effect actor class.
- * Used when an bullet hits an object and is destroyed.
- * 
- * @namespace Arena
- * @class Arena.BulletImpactEffect
- */
-(function()
-{
-   /**
-    * Constructor
-    */
-   Arena.BulletImpactEffect = function(p, v, enemy)
-   {
-      Arena.BulletImpactEffect.superclass.constructor.call(this, p, v, 3, function()
-         {
-            return new ArenaParticle(
-               p.clone(), v.nrotate(Rnd()*PIO8), ~~(Rnd() * 4), 0, 15, 10);
-         });
-      
-      return this;
-   };
-   
-   extend(Arena.BulletImpactEffect, Arena.Particles);
-})();
-
-
-/**
- * Player explosion - Particle effect actor class.
- * 
- * @namespace Arena
- * @class Arena.PlayerExplosion
- */
-(function()
-{
-   /**
-    * Constructor
-    */
-   Arena.PlayerExplosion = function(p, v)
-   {
-      Arena.PlayerExplosion.superclass.constructor.call(this, p, v, 20, function()
-         {
-            // randomise start position slightly
-            var pos = p.clone();
-            pos.x += randomInt(-5, 5);
-            pos.y += randomInt(-5, 5);
-            // randomise radial direction vector - speed and angle, then add parent vector
-            switch (randomInt(1,2))
-            {
-               case 1:
-                  var t = new Vector(0, randomInt(5, 8));
-                  t.rotate(Rnd() * TWOPI);
-                  t.add(v);
-                  return new ArenaParticle(
-                     pos, t, Rnd() * 5 + 5, 1, 25, 15, "white");
-               case 2:
-                  var t = new Vector(0, randomInt(5, 10));
-                  t.rotate(Rnd() * TWOPI);
-                  t.add(v);
-                  return new ArenaParticle(
-                     pos, t, ~~(Rnd() * 4 + 4), 2, 25, 15);
-            }
-         });
-      
-      return this;
-   };
-   
-   extend(Arena.PlayerExplosion, Arena.Particles);
-})();
-
-
-/**
- * Text indicator effect actor class.
- * 
- * @namespace Arena
- * @class Arena.TextIndicator
- */
-(function()
-{
-   Arena.TextIndicator = function(p, v, msg, textSize, colour, fadeLength)
-   {
-      this.fadeLength = (fadeLength ? fadeLength : this.DEFAULT_FADE_LENGTH);
-      Arena.TextIndicator.superclass.constructor.call(this, p, v, this.fadeLength);
-      this.msg = msg;
-      if (textSize)
-      {
-         this.textSize = textSize;
-      }
-      if (colour)
-      {
-         this.colour = colour;
-      }
-      return this;
-   };
-   
-   extend(Arena.TextIndicator, Game.EffectActor,
-   {
-      DEFAULT_FADE_LENGTH: 16,
-      fadeLength: 0,
-      textSize: 22,
-      msg: null,
-      colour: "rgb(255,255,255)",
-      
-      /**
-       * Text indicator effect rendering method
-       * 
-       * @param ctx {object} Canvas rendering context
-       */
-      onRender: function onRender(ctx, world)
-      {
-         ctx.save();
-         if (this.worldToScreen(ctx, world, 128))
-         {
-            var alpha = (1.0 / this.fadeLength) * this.lifespan;
-            ctx.globalAlpha = alpha;
-            ctx.shadowBlur = 0;
-            Game.fillText(ctx, this.msg, this.textSize + "pt Courier New", 0, 0, this.colour);
-         }
-         ctx.restore();
-      }
-   });
-})();
-
-
-/**
- * Score indicator effect actor class.
- * 
- * @namespace Arena
- * @class Arena.ScoreIndicator
- */
-(function()
-{
-   Arena.ScoreIndicator = function(p, v, score, textSize, prefix, colour, fadeLength)
-   {
-      var msg = score.toString();
-      if (prefix)
-      {
-         msg = prefix + ' ' + msg;
-      }
-      Arena.ScoreIndicator.superclass.constructor.call(this, p, v, msg, textSize, colour, fadeLength);
-      return this;
-   };
-   
-   extend(Arena.ScoreIndicator, Arena.TextIndicator,
-   {
-   });
-})();
-/**
- * CanvasMark HTML5 Canvas Rendering Benchmark - March 2013
- *
- * @email kevtoast at yahoo dot com
- * @twitter kevinroast
- *
- * (C) 2013 Kevin Roast
- * 
- * Please see: license.txt
- * You are welcome to use this code, but I would appreciate an email or tweet
- * if you do anything interesting with it!
- */
-
-
-window.addEventListener('load', onloadHandler, false);
-
-/**
- * Global window onload handler
- */
-var g_splashImg = new Image();
-function onloadHandler()
-{
-   // once the slash screen is loaded, bootstrap the main benchmark class
-   g_splashImg.src = 'images/canvasmark2013.jpg';
-   g_splashImg.onload = function()
-   {
-      // init our game with Game.Main derived instance
-      GameHandler.init();
-      GameHandler.start(new Benchmark.Main());
-   };
-}
-
-
-/**
- * Benchmark root namespace.
- * 
- * @namespace Benchmark
- */
-if (typeof Benchmark == "undefined" || !Benchmark)
-{
-   var Benchmark = {};
-}
-
-
-/**
- * Benchmark main class.
- * 
- * @namespace Benchmark
- * @class Benchmark.Main
- */
-(function()
-{
-   Benchmark.Main = function()
-   {
-      Benchmark.Main.superclass.constructor.call(this);
-      
-      // create the scenes that are directly part of the Benchmark container
-      var infoScene = new Benchmark.InfoScene(this);
-      
-      // add the info scene - must be added first
-      this.scenes.push(infoScene);
-      
-      // create the Test instances that the benchmark should manage
-      // each Test instance will add child scenes to the benchmark
-      var loader = new Game.Preloader();
-      this.asteroidsTest = new Asteroids.Test(this, loader);
-      this.arenaTest = new Arena.Test(this, loader);
-      this.featureTest = new Feature.Test(this, loader);
-      
-      // add benchmark completed scene
-      this.scenes.push(new Benchmark.CompletedScene(this));
-      
-      // the benchmark info scene is displayed first and responsible for allowing the
-      // benchmark to start once images required by the game engines have been loaded
-      loader.onLoadCallback(function() {
-         TalosContentProfiler.resume("begin", true).then(() => {
-           infoScene.ready();
-         });
-      });
-   };
-   
-   extend(Benchmark.Main, Game.Main,
-   {
-      asteroidsTest: null,
-      arenaTest: null,
-      featureTest: null,
-      
-      addBenchmarkScene: function addBenchmarkScene(scene)
-      {
-         this.scenes.push(scene);
-      }
-   });
-})();
-
-
-/**
- * Benchmark Benchmark Info Scene scene class.
- * 
- * @namespace Benchmark
- * @class Benchmark.InfoScene
- */
-(function()
-{
-   Benchmark.InfoScene = function(game)
-   {
-      this.game = game;
-      
-      // allow start via mouse click - also for starting benchmark on touch devices
-      var me = this;
-      var fMouseDown = function(e)
-      {
-         if (e.button == 0)
-         {
-            if (me.imagesLoaded)
-            {
-               me.start = true;
-               return true;
-            }
-         }
-      };
-      GameHandler.canvas.addEventListener("mousedown", fMouseDown, false);
-      
-      Benchmark.InfoScene.superclass.constructor.call(this, false, null);
-   };
-   
-   extend(Benchmark.InfoScene, Game.Scene,
-   {
-      game: null,
-      start: false,
-      imagesLoaded: false,
-      sceneStarted: null,
-      loadingMessage: false,
-      
-      /**
-       * Scene completion polling method
-       */
-      isComplete: function isComplete()
-      {
-         return this.start;
-      },
-      
-      onInitScene: function onInitScene()
-      {
-         this.playable = false;
-         this.start = false;
-         this.yoff = 1;
-      },
-      
-      onRenderScene: function onRenderScene(ctx)
-      {
-         ctx.save();
-         if (this.imagesLoaded)
-         {
-            // splash logo image dimensions
-            var w = 640, h = 640;
-            if (this.yoff < h - 1)
-            {
-               // liquid fill bg effect
-               ctx.drawImage(g_splashImg, 0, 0, w, this.yoff, 0, 0, w, this.yoff);
-               ctx.drawImage(g_splashImg, 0, this.yoff, w, 2, 0, this.yoff, w, h-this.yoff);
-               this.yoff++;
-            }
-            else
-            {
-               var toff = (GameHandler.height/2 + 196), tsize = 40;
-               ctx.drawImage(g_splashImg, 0, toff-tsize+12, w, tsize, 0, toff-tsize+12, w, tsize);
-               ctx.shadowBlur = 6;
-               ctx.shadowColor = "#000";
-               // alpha fade bounce in a single tertiary statement using a single counter
-               // first 64 values of 128 perform a fade in, for second 64 values, fade out
-               ctx.globalAlpha = (this.yoff % 128 < 64) ? ((this.yoff % 64) / 64) : (1 - ((this.yoff % 64) / 64));
-               
-               Game.centerFillText(ctx, "Click or press SPACE to run CanvasMark", "18pt Helvetica", toff, "#fff");
-            }
-            this.yoff++;
-         }
-         else if (!this.loadingMessage)
-         {
-            Game.centerFillText(ctx, "Please wait... Loading Images...", "18pt Helvetica", GameHandler.height/2, "#eee");
-            this.loadingMessage = true;
-         }
-         ctx.restore();
-      },
-      
-      /**
-       * Callback from image preloader when all images are ready
-       */
-      ready: function ready()
-      {
-         this.imagesLoaded = true;
-         if (location.search === "?auto=true")
-         {
-            this.start = true;
-         }
-      },
-      
-      onKeyDownHandler: function onKeyDownHandler(keyCode)
-      {
-         switch (keyCode)
-         {
-            case KEY.SPACE:
-            {
-               if (this.imagesLoaded)
-               {
-                  this.start = true;
-               }
-               return true;
-               break;
-            }
-         }
-      }
-   });
-})();
-
-
-/**
- * Benchmark CompletedScene scene class.
- * 
- * @namespace Benchmark
- * @class Benchmark.CompletedScene
- */
-(function()
-{
-   Benchmark.CompletedScene = function(game)
-   {
-      this.game = game;
-      
-      // construct the interval to represent the Game Over text effect
-      var interval = new Game.Interval("CanvasMark Completed!", this.intervalRenderer);
-      Benchmark.CompletedScene.superclass.constructor.call(this, false, interval);
-   };
-   
-   extend(Benchmark.CompletedScene, Game.Scene,
-   {
-      game: null,
-      exit: false,
-      
-      /**
-       * Scene init event handler
-       */
-      onInitScene: function onInitScene()
-      {
-         this.game.fps = 1;
-         this.interval.reset();
-         this.exit = false;
-      },
-      
-      /**
-       * Scene completion polling method
-       */
-      isComplete: function isComplete()
-      {
-         return true;
-      },
-      
-      intervalRenderer: function intervalRenderer(interval, ctx)
-      {
-         ctx.clearRect(0, 0, GameHandler.width, GameHandler.height);
-         var score = GameHandler.benchmarkScoreCount;
-         if (interval.framecounter === 0)
-         {
-            var browser = BrowserDetect.browser + " " + BrowserDetect.version;
-            var OS = BrowserDetect.OS;
-            
-            if (location.search === "?auto=true")
-            {
-				if ( typeof tpRecordTime !== "undefined" ) {
-				  tpRecordTime(GameHandler.benchmarkScores.join(','), 0, GameHandler.benchmarkLabels.join(','));
-				} else {
-				  alert(score);
-				}
-            }
-            else
-            {
-               // write results to browser
-               $("#results").html("<p>CanvasMark Score: " + score + " (" + browser + " on " + OS + ")</p>");
-               // tweet this result link
-               var tweet = "http://twitter.com/home/?status=" + browser + " (" + OS + ") scored " + score + " in the CanvasMark HTML5 benchmark! Test your browser: http://bit.ly/canvasmark %23javascript %23html5";
-               $("#tweetlink").attr('href', tweet.replace(/ /g, "%20"));
-               $("#results-wrapper").fadeIn();
-            }
-         }
-         Game.centerFillText(ctx, interval.label, "18pt Helvetica", GameHandler.height/2 - 32, "white");
-         Game.centerFillText(ctx, "Benchmark Score: " + score, "14pt Helvetica", GameHandler.height/2, "white");
-         
-         interval.complete = (this.exit || interval.framecounter++ > 400);
-      },
-      
-      onKeyDownHandler: function onKeyDownHandler(keyCode)
-      {
-         switch (keyCode)
-         {
-            case KEY.SPACE:
-            {
-               this.exit = true;
-               return true;
-               break;
-            }
-         }
-      }
-   });
-})();
-
-
-var BrowserDetect = {
-	init: function () {
-		this.browser = this.searchString(this.dataBrowser) || "Unknown Browser";
-		this.version = this.searchVersion(navigator.userAgent)
-			|| this.searchVersion(navigator.appVersion)
-			|| "an unknown version";
-		this.OS = this.searchString(this.dataOS) || "Unknown OS";
-	},
-	searchString: function (data) {
-		for (var i=0;i<data.length;i++)	{
-			var dataString = data[i].string;
-			var dataProp = data[i].prop;
-			this.versionSearchString = data[i].versionSearch || data[i].identity;
-			if (dataString) {
-				if (dataString.indexOf(data[i].subString) != -1)
-					return data[i].identity;
-			}
-			else if (dataProp)
-				return data[i].identity;
-		}
-	},
-	searchVersion: function (dataString) {
-		var index = dataString.indexOf(this.versionSearchString);
-		if (index == -1) return;
-		return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
-	},
-	dataBrowser: [
-		{
-			string: navigator.userAgent,
-			subString: "Chrome",
-			identity: "Chrome"
-		},
-		{ 	string: navigator.userAgent,
-			subString: "OmniWeb",
-			versionSearch: "OmniWeb/",
-			identity: "OmniWeb"
-		},
-		{
-			string: navigator.vendor,
-			subString: "Apple",
-			identity: "Safari",
-			versionSearch: "Version"
-		},
-		{
-			prop: window.opera,
-			identity: "Opera",
-			versionSearch: "Version"
-		},
-		{
-			string: navigator.vendor,
-			subString: "iCab",
-			identity: "iCab"
-		},
-		{
-			string: navigator.vendor,
-			subString: "KDE",
-			identity: "Konqueror"
-		},
-		{
-			string: navigator.userAgent,
-			subString: "Firefox",
-			identity: "Firefox"
-		},
-		{
-			string: navigator.vendor,
-			subString: "Camino",
-			identity: "Camino"
-		},
-		{		// for newer Netscapes (6+)
-			string: navigator.userAgent,
-			subString: "Netscape",
-			identity: "Netscape"
-		},
-		{
-			string: navigator.userAgent,
-			subString: "MSIE",
-			identity: "IE",
-			versionSearch: "MSIE"
-		},
-		{
-			string: navigator.userAgent,
-			subString: "Gecko",
-			identity: "Mozilla",
-			versionSearch: "rv"
-		},
-		{ 		// for older Netscapes (4-)
-			string: navigator.userAgent,
-			subString: "Mozilla",
-			identity: "Netscape",
-			versionSearch: "Mozilla"
-		}
-	],
-	dataOS : [
-		{
-			string: navigator.platform,
-			subString: "Win",
-			identity: "Windows"
-		},
-		{
-			string: navigator.platform,
-			subString: "Mac",
-			identity: "Mac"
-		},
-		{
-		   string: navigator.userAgent,
-		   subString: "iPhone",
-		   identity: "iOS"
-	   },
-	   {
-		   string: navigator.userAgent,
-		   subString: "iPod",
-		   identity: "iOS"
-	   },
-	   {
-		   string: navigator.userAgent,
-		   subString: "iPad",
-		   identity: "iOS"
-	   },
-		{
-			string: navigator.platform,
-			subString: "Linux",
-			identity: "Linux"
-		}
-	]
-};
-BrowserDetect.init();
deleted file mode 100644
--- a/testing/talos/talos/tests/canvasmark/scripts/jquery-1.4.2.min.js
+++ /dev/null
@@ -1,154 +0,0 @@
-/*!
- * jQuery JavaScript Library v1.4.2
- * http://jquery.com/
- *
- * Copyright 2010, John Resig
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- *
- * Includes Sizzle.js
- * http://sizzlejs.com/
- * Copyright 2010, The Dojo Foundation
- * Released under the MIT, BSD, and GPL Licenses.
- *
- * Date: Sat Feb 13 22:33:48 2010 -0500
- */
-(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o<i;o++)e(a[o],b,f?d.call(a[o],o,e(a[o],b)):d,j);return a}return i?
-e(a[0],b):w}function J(){return(new Date).getTime()}function Y(){return false}function Z(){return true}function na(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function oa(a){var b,d=[],f=[],e=arguments,j,i,o,k,n,r;i=c.data(this,"events");if(!(a.liveFired===this||!i||!i.live||a.button&&a.type==="click")){a.liveFired=this;var u=i.live.slice(0);for(k=0;k<u.length;k++){i=u[k];i.origType.replace(O,"")===a.type?f.push(i.selector):u.splice(k--,1)}j=c(a.target).closest(f,a.currentTarget);n=0;for(r=
-j.length;n<r;n++)for(k=0;k<u.length;k++){i=u[k];if(j[n].selector===i.selector){o=j[n].elem;f=null;if(i.preType==="mouseenter"||i.preType==="mouseleave")f=c(a.relatedTarget).closest(i.selector)[0];if(!f||f!==o)d.push({elem:o,handleObj:i})}}n=0;for(r=d.length;n<r;n++){j=d[n];a.currentTarget=j.elem;a.data=j.handleObj.data;a.handleObj=j.handleObj;if(j.handleObj.origHandler.apply(j.elem,e)===false){b=false;break}}return b}}function pa(a,b){return"live."+(a&&a!=="*"?a+".":"")+b.replace(/\./g,"`").replace(/ /g,
-"&")}function qa(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function ra(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var f=c.data(a[d++]),e=c.data(this,f);if(f=f&&f.events){delete e.handle;e.events={};for(var j in f)for(var i in f[j])c.event.add(this,j,f[j][i],f[j][i].data)}}})}function sa(a,b,d){var f,e,j;b=b&&b[0]?b[0].ownerDocument||b[0]:s;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===s&&!ta.test(a[0])&&(c.support.checkClone||!ua.test(a[0]))){e=
-true;if(j=c.fragments[a[0]])if(j!==1)f=j}if(!f){f=b.createDocumentFragment();c.clean(a,b,f,d)}if(e)c.fragments[a[0]]=j?f:1;return{fragment:f,cacheable:e}}function K(a,b){var d={};c.each(va.concat.apply([],va.slice(0,b)),function(){d[this]=a});return d}function wa(a){return"scrollTo"in a&&a.document?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var c=function(a,b){return new c.fn.init(a,b)},Ra=A.jQuery,Sa=A.$,s=A.document,T,Ta=/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/,
-Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&&
-(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this,
-a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b===
-"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,
-function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b<d;b++)if((e=arguments[b])!=null)for(j in e){i=a[j];o=e[j];if(a!==o)if(f&&o&&(c.isPlainObject(o)||c.isArray(o))){i=i&&(c.isPlainObject(i)||
-c.isArray(i))?i:c.isArray(o)?[]:{};a[j]=c.extend(f,i,o)}else if(o!==w)a[j]=o}return a};c.extend({noConflict:function(a){A.$=Sa;if(a)A.jQuery=Ra;return c},isReady:false,ready:function(){if(!c.isReady){if(!s.body)return setTimeout(c.ready,13);c.isReady=true;if(Q){for(var a,b=0;a=Q[b++];)a.call(s,c);Q=null}c.fn.triggerHandler&&c(s).triggerHandler("ready")}},bindReady:function(){if(!xa){xa=true;if(s.readyState==="complete")return c.ready();if(s.addEventListener){s.addEventListener("DOMContentLoaded",
-L,false);A.addEventListener("load",c.ready,false)}else if(s.attachEvent){s.attachEvent("onreadystatechange",L);A.attachEvent("onload",c.ready);var a=false;try{a=A.frameElement==null}catch(b){}s.documentElement.doScroll&&a&&ma()}}},isFunction:function(a){return $.call(a)==="[object Function]"},isArray:function(a){return $.call(a)==="[object Array]"},isPlainObject:function(a){if(!a||$.call(a)!=="[object Object]"||a.nodeType||a.setInterval)return false;if(a.constructor&&!aa.call(a,"constructor")&&!aa.call(a.constructor.prototype,
-"isPrototypeOf"))return false;var b;for(b in a);return b===w||aa.call(a,b)},isEmptyObject:function(a){for(var b in a)return false;return true},error:function(a){throw a;},parseJSON:function(a){if(typeof a!=="string"||!a)return null;a=c.trim(a);if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return A.JSON&&A.JSON.parse?A.JSON.parse(a):(new Function("return "+
-a))();else c.error("Invalid JSON: "+a)},noop:function(){},globalEval:function(a){if(a&&Va.test(a)){var b=s.getElementsByTagName("head")[0]||s.documentElement,d=s.createElement("script");d.type="text/javascript";if(c.support.scriptEval)d.appendChild(s.createTextNode(a));else d.text=a;b.insertBefore(d,b.firstChild);b.removeChild(d)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,b,d){var f,e=0,j=a.length,i=j===w||c.isFunction(a);if(d)if(i)for(f in a){if(b.apply(a[f],
-d)===false)break}else for(;e<j;){if(b.apply(a[e++],d)===false)break}else if(i)for(f in a){if(b.call(a[f],f,a[f])===false)break}else for(d=a[0];e<j&&b.call(d,e,d)!==false;d=a[++e]);return a},trim:function(a){return(a||"").replace(Wa,"")},makeArray:function(a,b){b=b||[];if(a!=null)a.length==null||typeof a==="string"||c.isFunction(a)||typeof a!=="function"&&a.setInterval?ba.call(b,a):c.merge(b,a);return b},inArray:function(a,b){if(b.indexOf)return b.indexOf(a);for(var d=0,f=b.length;d<f;d++)if(b[d]===
-a)return d;return-1},merge:function(a,b){var d=a.length,f=0;if(typeof b.length==="number")for(var e=b.length;f<e;f++)a[d++]=b[f];else for(;b[f]!==w;)a[d++]=b[f++];a.length=d;return a},grep:function(a,b,d){for(var f=[],e=0,j=a.length;e<j;e++)!d!==!b(a[e],e)&&f.push(a[e]);return f},map:function(a,b,d){for(var f=[],e,j=0,i=a.length;j<i;j++){e=b(a[j],j,d);if(e!=null)f[f.length]=e}return f.concat.apply([],f)},guid:1,proxy:function(a,b,d){if(arguments.length===2)if(typeof b==="string"){d=a;a=d[b];b=w}else if(b&&
-!c.isFunction(b)){d=b;b=w}if(!b&&a)b=function(){return a.apply(d||this,arguments)};if(a)b.guid=a.guid=a.guid||b.guid||c.guid++;return b},uaMatch:function(a){a=a.toLowerCase();a=/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version)?[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||!/compatible/.test(a)&&/(mozilla)(?:.*? rv:([\w.]+))?/.exec(a)||[];return{browser:a[1]||"",version:a[2]||"0"}},browser:{}});P=c.uaMatch(P);if(P.browser){c.browser[P.browser]=true;c.browser.version=P.version}if(c.browser.webkit)c.browser.safari=
-true;if(ya)c.inArray=function(a,b){return ya.call(b,a)};T=c(s);if(s.addEventListener)L=function(){s.removeEventListener("DOMContentLoaded",L,false);c.ready()};else if(s.attachEvent)L=function(){if(s.readyState==="complete"){s.detachEvent("onreadystatechange",L);c.ready()}};(function(){c.support={};var a=s.documentElement,b=s.createElement("script"),d=s.createElement("div"),f="script"+J();d.style.display="none";d.innerHTML="   <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
-var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected,
-parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent=
-false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n=
-s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true,
-applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando];
-else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,
-a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===
-w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i,
-cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1)if(e.className){for(var j=" "+e.className+" ",
-i=e.className,o=0,k=b.length;o<k;o++)if(j.indexOf(" "+b[o]+" ")<0)i+=" "+b[o];e.className=c.trim(i)}else e.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(k){var n=c(this);n.removeClass(a.call(this,k,n.attr("class")))});if(a&&typeof a==="string"||a===w)for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1&&e.className)if(a){for(var j=(" "+e.className+" ").replace(Aa," "),i=0,o=b.length;i<o;i++)j=j.replace(" "+b[i]+" ",
-" ");e.className=c.trim(j)}else e.className=""}return this},toggleClass:function(a,b){var d=typeof a,f=typeof b==="boolean";if(c.isFunction(a))return this.each(function(e){var j=c(this);j.toggleClass(a.call(this,e,j.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var e,j=0,i=c(this),o=b,k=a.split(ca);e=k[j++];){o=f?o:!i.hasClass(e);i[o?"addClass":"removeClass"](e)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,"__className__",this.className);this.className=
-this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(Aa," ").indexOf(a)>-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j<d;j++){var i=
-e[j];if(i.selected){a=c(i).val();if(b)return a;f.push(a)}}return f}if(Ba.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Za,"")}return w}var o=c.isFunction(a);return this.each(function(k){var n=c(this),r=a;if(this.nodeType===1){if(o)r=a.call(this,k,n.val());if(typeof r==="number")r+="";if(c.isArray(r)&&Ba.test(this.type))this.checked=c.inArray(n.val(),r)>=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected=
-c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");
-a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g,
-function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split(".");
-k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a),
-C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B<r.length;B++){u=r[B];if(d.guid===u.guid){if(i||k.test(u.namespace)){f==null&&r.splice(B--,1);n.remove&&n.remove.call(a,u)}if(f!=
-null)break}}if(r.length===0||f!=null&&r.length===1){if(!n.teardown||n.teardown.call(a,o)===false)Ca(a,e,z.handle);delete C[e]}}else for(var B=0;B<r.length;B++){u=r[B];if(i||k.test(u.namespace)){c.event.remove(a,n,u.handler,B);r.splice(B--,1)}}}if(c.isEmptyObject(C)){if(b=z.handle)b.elem=null;delete z.events;delete z.handle;c.isEmptyObject(z)&&c.removeData(a)}}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[G]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=
-e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&&
-f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;
-if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e<j;e++){var i=d[e];if(b||f.test(i.namespace)){a.handler=i.handler;a.data=i.data;a.handleObj=i;i=i.handler.apply(this,arguments);if(i!==w){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
-fix:function(a){if(a[G])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement||s;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=s.documentElement;d=s.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
-d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==w)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,a.origType,c.extend({},a,{handler:oa}))},remove:function(a){var b=true,d=a.origType.replace(O,"");c.each(c.data(this,
-"events").live||[],function(){if(d===this.origType.replace(O,""))return b=false});b&&c.event.remove(this,a.origType,oa)}},beforeunload:{setup:function(a,b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};var Ca=s.removeEventListener?function(a,b,d){a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=
-a;this.type=a.type}else this.type=a;this.timeStamp=J();this[G]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=Z;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=Z;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z;this.stopPropagation()},isDefaultPrevented:Y,isPropagationStopped:Y,
-isImmediatePropagationStopped:Y};var Da=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},Ea=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ea:Da,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ea:Da)}}});if(!c.support.submitBubbles)c.event.special.submit=
-{setup:function(){if(this.nodeName.toLowerCase()!=="form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length)return na("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13)return na("submit",this,arguments)})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};
-if(!c.support.changeBubbles){var da=/textarea|input|select/i,ea,Fa=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",
-e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,
-"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a,
-d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j<o;j++)c.event.add(this[j],d,i,f)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&
-!a.preventDefault)for(var d in a)this.unbind(d,a[d]);else{d=0;for(var f=this.length;d<f;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,f){return this.live(b,d,f,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},
-toggle:function(a){for(var b=arguments,d=1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(f){var e=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,e+1);f.preventDefault();return b[e].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var Ga={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,f,e,j){var i,o=0,k,n,r=j||this.selector,
-u=j?this:c(this.context);if(c.isFunction(f)){e=f;f=w}for(d=(d||"").split(" ");(i=d[o++])!=null;){j=O.exec(i);k="";if(j){k=j[0];i=i.replace(O,"")}if(i==="hover")d.push("mouseenter"+k,"mouseleave"+k);else{n=i;if(i==="focus"||i==="blur"){d.push(Ga[i]+k);i+=k}else i=(Ga[i]||i)+k;b==="live"?u.each(function(){c.event.add(this,pa(i,r),{data:f,selector:r,handler:e,origType:i,origHandler:e,preType:n})}):u.unbind(pa(i,r),e)}}return this}});c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),
-function(a,b){c.fn[b]=function(d){return d?this.bind(b,d):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});A.attachEvent&&!A.addEventListener&&A.attachEvent("onunload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});(function(){function a(g){for(var h="",l,m=0;g[m];m++){l=g[m];if(l.nodeType===3||l.nodeType===4)h+=l.nodeValue;else if(l.nodeType!==8)h+=a(l.childNodes)}return h}function b(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];
-if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1&&!p){t.sizcache=l;t.sizset=q}if(t.nodeName.toLowerCase()===h){y=t;break}t=t[g]}m[q]=y}}}function d(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1){if(!p){t.sizcache=l;t.sizset=q}if(typeof h!=="string"){if(t===h){y=true;break}}else if(k.filter(h,[t]).length>0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
-e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift();
-t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D||
-g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h<g.length;h++)g[h]===g[h-1]&&g.splice(h--,1)}return g};k.matches=function(g,h){return k(g,null,null,h)};k.find=function(g,h,l){var m,q;if(!g)return[];
-for(var p=0,v=n.order.length;p<v;p++){var t=n.order[p];if(q=n.leftMatch[t].exec(g)){var y=q[1];q.splice(1,1);if(y.substr(y.length-1)!=="\\"){q[1]=(q[1]||"").replace(/\\/g,"");m=n.find[t](q,h,l);if(m!=null){g=g.replace(n.match[t],"");break}}}}m||(m=h.getElementsByTagName("*"));return{set:m,expr:g}};k.filter=function(g,h,l,m){for(var q=g,p=[],v=h,t,y,S=h&&h[0]&&x(h[0]);g&&h.length;){for(var H in n.filter)if((t=n.leftMatch[H].exec(g))!=null&&t[2]){var M=n.filter[H],I,D;D=t[1];y=false;t.splice(1,1);if(D.substr(D.length-
-1)!=="\\"){if(v===p)p=[];if(n.preFilter[H])if(t=n.preFilter[H](t,v,l,p,m,S)){if(t===true)continue}else y=I=true;if(t)for(var U=0;(D=v[U])!=null;U++)if(D){I=M(D,t,U,v);var Ha=m^!!I;if(l&&I!=null)if(Ha)y=true;else v[U]=false;else if(Ha){p.push(D);y=true}}if(I!==w){l||(v=p);g=g.replace(n.match[H],"");if(!y)return[];break}}}if(g===q)if(y==null)k.error(g);else break;q=g}return v};k.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var n=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
-CLASS:/\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},
-relative:{"+":function(g,h){var l=typeof h==="string",m=l&&!/\W/.test(h);l=l&&!m;if(m)h=h.toLowerCase();m=0;for(var q=g.length,p;m<q;m++)if(p=g[m]){for(;(p=p.previousSibling)&&p.nodeType!==1;);g[m]=l||p&&p.nodeName.toLowerCase()===h?p||false:p===h}l&&k.filter(h,g,true)},">":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m<q;m++){var p=g[m];if(p){l=p.parentNode;g[m]=l.nodeName.toLowerCase()===h?l:false}}}else{m=0;for(q=g.length;m<q;m++)if(p=g[m])g[m]=
-l?p.parentNode:p.parentNode===h;l&&k.filter(h,g,true)}},"":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("parentNode",h,m,g,p,l)},"~":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("previousSibling",h,m,g,p,l)}},find:{ID:function(g,h,l){if(typeof h.getElementById!=="undefined"&&!l)return(g=h.getElementById(g[1]))?[g]:[]},NAME:function(g,h){if(typeof h.getElementsByName!=="undefined"){var l=[];
-h=h.getElementsByName(g[1]);for(var m=0,q=h.length;m<q;m++)h[m].getAttribute("name")===g[1]&&l.push(h[m]);return l.length===0?null:l}},TAG:function(g,h){return h.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,h,l,m,q,p){g=" "+g[1].replace(/\\/g,"")+" ";if(p)return g;p=0;for(var v;(v=h[p])!=null;p++)if(v)if(q^(v.className&&(" "+v.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},
-CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m,
-g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},
-text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},
-setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return h<l[3]-0},gt:function(g,h,l){return h>l[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h=
-h[3];l=0;for(m=h.length;l<m;l++)if(h[l]===g)return false;return true}else k.error("Syntax error, unrecognized expression: "+q)},CHILD:function(g,h){var l=h[1],m=g;switch(l){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(l==="first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":l=h[2];var q=h[3];if(l===1&&q===0)return true;h=h[0];var p=g.parentNode;if(p&&(p.sizcache!==h||!g.nodeIndex)){var v=0;for(m=p.firstChild;m;m=
-m.nextSibling)if(m.nodeType===1)m.nodeIndex=++v;p.sizcache=h}g=g.nodeIndex-q;return l===0?g===0:g%l===0&&g/l>=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m===
-"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g,
-h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l<m;l++)h.push(g[l]);else for(l=0;g[l];l++)h.push(g[l]);return h}}var B;if(s.documentElement.compareDocumentPosition)B=function(g,h){if(!g.compareDocumentPosition||
-!h.compareDocumentPosition){if(g==h)i=true;return g.compareDocumentPosition?-1:1}g=g.compareDocumentPosition(h)&4?-1:g===h?0:1;if(g===0)i=true;return g};else if("sourceIndex"in s.documentElement)B=function(g,h){if(!g.sourceIndex||!h.sourceIndex){if(g==h)i=true;return g.sourceIndex?-1:1}g=g.sourceIndex-h.sourceIndex;if(g===0)i=true;return g};else if(s.createRange)B=function(g,h){if(!g.ownerDocument||!h.ownerDocument){if(g==h)i=true;return g.ownerDocument?-1:1}var l=g.ownerDocument.createRange(),m=
-h.ownerDocument.createRange();l.setStart(g,0);l.setEnd(g,0);m.setStart(h,0);m.setEnd(h,0);g=l.compareBoundaryPoints(Range.START_TO_END,m);if(g===0)i=true;return g};(function(){var g=s.createElement("div"),h="script"+(new Date).getTime();g.innerHTML="<a name='"+h+"'/>";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&&
-q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML="<a href='#'></a>";
-if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="<p class='TEST'></p>";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}();
-(function(){var g=s.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}:
-function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q<p;q++)k(g,h[q],l);return k.filter(m,l)};c.find=k;c.expr=k.selectors;c.expr[":"]=c.expr.filters;c.unique=k.uniqueSort;c.text=a;c.isXMLDoc=x;c.contains=E})();var eb=/Until$/,fb=/^(?:parents|prevUntil|prevAll)/,
-gb=/,/;R=Array.prototype.slice;var Ia=function(a,b,d){if(c.isFunction(b))return c.grep(a,function(e,j){return!!b.call(e,j,e)===d});else if(b.nodeType)return c.grep(a,function(e){return e===b===d});else if(typeof b==="string"){var f=c.grep(a,function(e){return e.nodeType===1});if(Ua.test(b))return c.filter(b,f,!d);else b=c.filter(b,f)}return c.grep(a,function(e){return c.inArray(e,b)>=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f<e;f++){d=b.length;
-c.find(a,this[f],b);if(f>0)for(var j=d;j<b.length;j++)for(var i=0;i<d;i++)if(b[i]===b[j]){b.splice(j--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,f=b.length;d<f;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(Ia(this,a,false),"not",a)},filter:function(a){return this.pushStack(Ia(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j=
-{},i;if(f&&a.length){e=0;for(var o=a.length;e<o;e++){i=a[e];j[i]||(j[i]=c.expr.match.POS.test(i)?c(i,b||this.context):i)}for(;f&&f.ownerDocument&&f!==b;){for(i in j){e=j[i];if(e.jquery?e.index(f)>-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a===
-"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",
-d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?
-a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType===
-1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/<tbody/i,jb=/<|&#?\w+;/,ta=/<script|<object|<embed|<option|<style/i,ua=/checked\s*(?:[^=]|=\s*.checked.)/i,Ma=function(a,b,d){return hb.test(d)?
-a:b+"></"+d+">"},F={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=
-c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},
-wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},
-prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,
-this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);
-return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja,
-""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(f){this.empty().append(a)}}else c.isFunction(a)?this.each(function(e){var j=c(this),i=j.html();j.empty().append(function(){return a.call(this,e,i)})}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&
-this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),f=d.html();d.replaceWith(a.call(this,b,f))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){function f(u){return c.nodeName(u,"table")?u.getElementsByTagName("tbody")[0]||
-u.appendChild(u.ownerDocument.createElement("tbody")):u}var e,j,i=a[0],o=[],k;if(!c.support.checkClone&&arguments.length===3&&typeof i==="string"&&ua.test(i))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(i))return this.each(function(u){var z=c(this);a[0]=i.call(this,u,b?z.html():w);z.domManip(a,b,d)});if(this[0]){e=i&&i.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:sa(a,this,o);k=e.fragment;if(j=k.childNodes.length===
-1?(k=k.firstChild):k.firstChild){b=b&&c.nodeName(j,"tr");for(var n=0,r=this.length;n<r;n++)d.call(b?f(this[n],j):this[n],n>0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]);
-return this}else{e=0;for(var j=d.length;e<j;e++){var i=(e>0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["",
-""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]==="<table>"&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e=
-c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]?
-c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja=
-function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter=
-Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a,
-"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f=
-a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=
-a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=/<script(.|\s)*?\/script>/gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!==
-"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("<div />").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this},
-serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),
-function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,
-global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&&
-e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)?
-"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache===
-false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B=
-false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since",
-c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E||
-d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x);
-g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===
-1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b===
-"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional;
-if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");
-this[a].style.display=d||"";if(c.css(this[a],"display")==="none"){d=this[a].nodeName;var f;if(la[d])f=la[d];else{var e=c("<"+d+" />").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a<b;a++)this[a].style.display=c.data(this[a],"olddisplay")||"";return this}},hide:function(a,b){if(a||a===0)return this.animate(K("hide",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");!d&&d!=="none"&&c.data(this[a],
-"olddisplay",c.css(this[a],"display"))}a=0;for(b=this.length;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b){var d=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||d?this.each(function(){var f=d?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(K("toggle",3),a,b);return this},fadeTo:function(a,b,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d)},
-animate:function(a,b,d,f){var e=c.speed(b,d,f);if(c.isEmptyObject(a))return this.each(e.complete);return this[e.queue===false?"each":"queue"](function(){var j=c.extend({},e),i,o=this.nodeType===1&&c(this).is(":hidden"),k=this;for(i in a){var n=i.replace(ia,ja);if(i!==n){a[n]=a[i];delete a[i];i=n}if(a[i]==="hide"&&o||a[i]==="show"&&!o)return j.complete.call(this);if((i==="height"||i==="width")&&this.style){j.display=c.css(this,"display");j.overflow=this.style.overflow}if(c.isArray(a[i])){(j.specialEasing=
-j.specialEasing||{})[i]=a[i][1];a[i]=a[i][0]}}if(j.overflow!=null)this.style.overflow="hidden";j.curAnim=c.extend({},a);c.each(a,function(r,u){var z=new c.fx(k,j,r);if(Ab.test(u))z[u==="toggle"?o?"show":"hide":u](a);else{var C=Bb.exec(u),B=z.cur(true)||0;if(C){u=parseFloat(C[2]);var E=C[3]||"px";if(E!=="px"){k.style[r]=(u||1)+E;B=(u||1)/z.cur(true)*B;k.style[r]=B+E}if(C[1])u=(C[1]==="-="?-1:1)*u+B;z.custom(B,u,E)}else z.custom(B,u,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);
-this.each(function(){for(var f=d.length-1;f>=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration===
-"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||
-c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;
-this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=
-this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem,
-e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||
-c.fx.stop()},stop:function(){clearInterval(W);W=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===b.elem}).length};c.fn.offset="getBoundingClientRect"in s.documentElement?
-function(a){var b=this[0];if(a)return this.each(function(e){c.offset.setOffset(this,a,e)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);var d=b.getBoundingClientRect(),f=b.ownerDocument;b=f.body;f=f.documentElement;return{top:d.top+(self.pageYOffset||c.support.boxModel&&f.scrollTop||b.scrollTop)-(f.clientTop||b.clientTop||0),left:d.left+(self.pageXOffset||c.support.boxModel&&f.scrollLeft||b.scrollLeft)-(f.clientLeft||b.clientLeft||0)}}:function(a){var b=
-this[0];if(a)return this.each(function(r){c.offset.setOffset(this,a,r)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d=b.offsetParent,f=b,e=b.ownerDocument,j,i=e.documentElement,o=e.body;f=(e=e.defaultView)?e.getComputedStyle(b,null):b.currentStyle;for(var k=b.offsetTop,n=b.offsetLeft;(b=b.parentNode)&&b!==o&&b!==i;){if(c.offset.supportsFixedPosition&&f.position==="fixed")break;j=e?e.getComputedStyle(b,null):b.currentStyle;
-k-=b.scrollTop;n-=b.scrollLeft;if(b===d){k+=b.offsetTop;n+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(b.nodeName))){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=d;d=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&j.overflow!=="visible"){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=j}if(f.position==="relative"||f.position==="static"){k+=o.offsetTop;n+=o.offsetLeft}if(c.offset.supportsFixedPosition&&
-f.position==="fixed"){k+=Math.max(i.scrollTop,o.scrollTop);n+=Math.max(i.scrollLeft,o.scrollLeft)}return{top:k,left:n}};c.offset={initialize:function(){var a=s.body,b=s.createElement("div"),d,f,e,j=parseFloat(c.curCSS(a,"marginTop",true))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
-a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b);
-c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a,
-d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top-
-f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset":
-"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in
-e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window);
deleted file mode 100644
--- a/testing/talos/talos/tests/canvasmark/scripts/k3d-min.js
+++ /dev/null
@@ -1,1 +0,0 @@
-var DEBUG={};if(typeof K3D=="undefined"||!K3D){var K3D={}}(function(){K3D.BaseController=function(){this.objects=[];this.lights=[];this.renderers=[];this.renderers.point=new K3D.PointRenderer();this.renderers.wireframe=new K3D.WireframeRenderer();this.renderers.solid=new K3D.SolidRenderer()};K3D.BaseController.prototype={renderers:null,objects:null,lights:null,sort:true,clearBackground:true,clearingStrategy:"all",fillStyle:null,addK3DObject:function(a){a.setController(this);this.objects.push(a)},addLightSource:function(a){this.lights.push(a)},getRenderer:function(a){return this.renderers[a]},resetBackground:function(j){if(this.clearBackground){if(this.fillStyle){j.fillStyle=this.fillStyle}switch(this.clearingStrategy){case"all":if(this.fillStyle){j.fillRect(0,0,this.canvas.width,this.canvas.height)}else{j.clearRect(0,0,this.canvas.width,this.canvas.height)}case"eachobject":for(var d=0,e=this.objects.length,c,b;d<e;d++){c=this.objects[d];switch(c.drawmode){case"point":b=(c.outputlinescale?c.outputlinescale:c.linescale)+1;if(this.fillStyle){j.fillRect(Floor(c.rminx)-b,Floor(c.rminy)-b,(Ceil(c.rmaxx)-Floor(c.rminx))+b*2+1,(Ceil(c.rmaxy)-Floor(c.rminy))+b*2+1)}else{j.clearRect(Floor(c.rminx)-b,Floor(c.rminy)-b,(Ceil(c.rmaxx)-Floor(c.rminx))+b*2+1,(Ceil(c.rmaxy)-Floor(c.rminy))+b*2+1)}break;case"wireframe":b=(c.outputlinescale?c.outputlinescale:c.linescale)*2;if(this.fillStyle){j.fillRect(Floor(c.rminx)-b,Floor(c.rminy)-b,(Ceil(c.rmaxx)-Floor(c.rminx))+b*2+1,(Ceil(c.rmaxy)-Floor(c.rminy))+b*2+1)}else{j.clearRect(Floor(c.rminx)-b,Floor(c.rminy)-b,(Ceil(c.rmaxx)-Floor(c.rminx))+b*2+1,(Ceil(c.rmaxy)-Floor(c.rminy))+b*2+1)}break;case"solid":if(this.fillStyle){j.fillRect(Floor(c.rminx)-1,Floor(c.rminy)-1,(Ceil(c.rmaxx)-Floor(c.rminx))+1,(Ceil(c.rmaxy)-Floor(c.rminy))+1)}else{j.clearRect(Floor(c.rminx)-1,Floor(c.rminy)-1,Ceil(c.rmaxx)-Floor(c.rminx)+1,Ceil(c.rmaxy)-Floor(c.rminy)+1)}break}}case"combineobjects":var a=1,k=4096,g=-4096,h=4096,f=-4096;for(var d=0,e=this.objects.length,c,b;d<e;d++){c=this.objects[d];switch(c.drawmode){case"point":b=(c.outputlinescale?c.outputlinescale:c.linescale)+1;if(b>a){a=b}break;case"wireframe":b=(c.outputlinescale?c.outputlinescale:c.linescale)*2;if(b>a){a=b}break}if(c.rminx<k){k=c.rminx}if(c.rmaxx>g){g=c.rmaxx}if(c.rminy<h){h=c.rminy}if(c.rmaxy>f){f=c.rmaxy}}if(this.fillStyle){j.fillRect(Floor(k)-a,Floor(h)-a,(Ceil(g)-Floor(k))+a*2+1,(Ceil(f)-Floor(h))+a*2+1)}else{j.clearRect(Floor(k)-a,Floor(h)-a,(Ceil(g)-Floor(k))+a*2+1,(Ceil(f)-Floor(h))+a*2+1)}}}},processFrame:function(c){var g=this.objects;for(var f=0,a=g.length;f<a;f++){g[f].executePipeline()}var e=this.lights;for(var f=0,a=e.length;f<a;f++){e[f].executePipeline()}if(this.sort){g.forEach(function b(k,j,h){k.averagez=null});g.sort(function d(i,h){if(i.averagez===null){i.calculateAverageZ()}if(h.averagez===null){h.calculateAverageZ()}return(i.averagez<h.averagez?1:-1)})}for(var f=0,a=g.length;f<a;f++){c.save();g[f].executeRenderer(c);c.restore()}}}})();(function(){K3D.Controller=function(b,a){K3D.Controller.superclass.constructor.call(this);this.canvas=b;var c=this;if(!a){b.onclick=function(d){c.paused=!c.paused;if(!c.paused){c.frame()}}}};extend(K3D.Controller,K3D.BaseController,{canvas:null,paused:true,callback:null,fps:40,lastFrameStart:0,addK3DObject:function(a){a.setController(this,this.canvas.width,this.canvas.height);this.objects.push(a)},tick:function(){this.frame()},frame:function(){var f=new Date().getTime();if(this.callback){this.callback.call(this)}var a=this.canvas.getContext("2d");if(this.lastFrameStart){this.resetBackground(a)}this.processFrame(a);var b=1000/this.fps;var e=(new Date().getTime()-f);if(!this.paused){var d=this;setTimeout(function(){d.frame()},b-e<=0?1:b-e)}if(DEBUG&&DEBUG.FPS){a.fillStyle="grey";a.fillText("TPF: "+e,4,16);var c=Math.round(1000/(f-this.lastFrameStart));a.fillText("FPS: "+c,4,24)}this.lastFrameStart=f}})})();(function(){K3D.RequestAnimController=function(b,a){K3D.Controller.superclass.constructor.call(this);this.canvas=b;this.framecount=1;var c=this;if(!a){b.onclick=function(d){c.paused=!c.paused;if(!c.paused){c.frame()}}}};extend(K3D.RequestAnimController,K3D.BaseController,{canvas:null,paused:true,callback:null,totalfps:0,framecount:0,lastFrameStart:0,addK3DObject:function(a){a.setController(this,this.canvas.width,this.canvas.height);this.objects.push(a)},frame:function(){var g=Date.now();if(this.callback){this.callback.call(this)}var b=this.canvas.getContext("2d");this.resetBackground(b);this.processFrame(b);var f=(Date.now()-g);if(!this.paused){var a=~~(1000/60-f),e=this;requestAnimFrame(function(){e.frame.call(e)},a)}if(DEBUG&&DEBUG.FPS){b.save();var d=Math.round(1000/(g-this.lastFrameStart)),c=Math.round((this.totalfps+=d)/(this.framecount++));b.fillStyle="white";b.fillRect(4,4,128,10);b.fillStyle="black";b.fillText("TPF: "+f+" FPS: "+d+" AVF: "+c,4,12);b.restore()}this.lastFrameStart=g}})})();window.requestAnimFrame=(function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(b,a){window.setTimeout(b,a)}})();(function(){K3D.BaseObject=function(){this.matrix=new Array(3);for(var a=0;a<3;a++){this.matrix[a]=new Array(3)}this.angles=new Array(6);return this};K3D.BaseObject.prototype={matrix:null,angles:null,offx:0,offy:0,offz:0,aboutx:0,abouty:0,aboutz:0,ogamma:0,otheta:0,ophi:0,addgamma:0,addtheta:0,addphi:0,velx:0,vely:0,velz:0,bminx:0,bminy:0,bminz:0,bmaxx:0,bmaxy:0,bmaxz:0,rminx:0,rminy:0,rmaxx:0,rmaxy:0,doublesided:false,calcMatrix:function(){var b=this.angles,a=this.matrix;b[0]=Sin(this.ogamma*RAD);b[1]=Cos(this.ogamma*RAD);b[2]=Sin(this.otheta*RAD);b[3]=Cos(this.otheta*RAD);b[4]=Sin(this.ophi*RAD);b[5]=Cos(this.ophi*RAD);a[0][0]=b[5]*b[1];a[1][0]=-(b[5]*b[0]);a[2][0]=b[4];a[0][1]=(b[2]*b[4]*b[1])+(b[3]*b[0]);a[1][1]=(b[3]*b[1])-(b[2]*b[4]*b[0]);a[2][1]=-(b[2]*b[5]);a[0][2]=(b[2]*b[0])-(b[3]*b[4]*b[1]);a[1][2]=(b[2]*b[1])+(b[3]*b[4]*b[0]);a[2][2]=b[3]*b[5]},transformToWorld:function(){},executePipeline:function(){this.ogamma+=this.addgamma;this.otheta+=this.addtheta;this.ophi+=this.addphi;this.offx+=this.velx;this.offy+=this.vely;this.offz+=this.velz;if(this.offx<this.bminx||this.offx>this.bmaxx){this.velx*=-1}if(this.offy<this.bminy||this.offy>this.bmaxy){this.vely*=-1}if(this.offz<this.bminz||this.offz>this.bmaxz){this.velz*=-1}this.calcMatrix();this.transformToWorld()}}})();(function(){K3D.K3DObject=function(){K3D.K3DObject.superclass.constructor.call(this);this.textures=[];return this};extend(K3D.K3DObject,K3D.BaseObject,{controller:null,worldcoords:null,screenx:0,screeny:0,depthscale:0,linescale:2,color:null,drawmode:"point",shademode:"depthcue",sortmode:"sorted",fillmode:"filltwice",perslevel:1024,scale:0,recalculateNormals:false,points:null,edges:null,faces:null,screencoords:null,averagez:null,textures:null,depthcueColors:null,init:function(v,l,f){this.points=v;this.edges=l;this.faces=f;this.worldcoords=new Array(v.length+f.length);for(var n=0,k=this.worldcoords.length;n<k;n++){this.worldcoords[n]={x:0,y:0,z:0}}this.screencoords=new Array(v.length);for(var n=0,k=this.screencoords.length;n<k;n++){this.screencoords[n]={x:0,y:0}}if(this.scale!==0){for(var n=0,k=this.points.length;n<k;n++){v[n].x*=this.scale;v[n].y*=this.scale;v[n].z*=this.scale}}if(this.color===null){this.color=[255,255,255]}this.depthcueColors=new Array(256);for(var q=0,a,o,t;q<256;q++){a=this.color[0]*(q/255);o=this.color[1]*(q/255);t=this.color[2]*(q/255);this.depthcueColors[q]="rgb("+Ceil(a)+","+Ceil(o)+","+Ceil(t)+")"}for(var n=0,k=f.length,p,e,u,m,d,s,h;n<k;n++){p=f[n].vertices;e=v[p[1]].x-v[p[0]].x;u=v[p[1]].y-v[p[0]].y;m=v[p[1]].z-v[p[0]].z;d=v[p[2]].x-v[p[0]].x;s=v[p[2]].y-v[p[0]].y;h=v[p[2]].z-v[p[0]].z;f[n].normal=calcNormalVector(e,u,m,d,s,h);if(!f[n].color){f[n].color=this.color}if(f[n].texture===undefined){f[n].texture=null}}},setController:function(a,b,c){this.controller=a;if(b){this.screenx=b/2;this.screeny=c/2;this.depthscale=this.screenx;this.bminx=-this.screenx;this.bminy=-this.screeny;this.bminz=-this.screenx;this.bmaxx=this.screenx;this.bmaxy=this.screeny;this.bmaxz=this.screenx}},transformToWorld:function(){var k,h,f;var p=this.points,e=this.worldcoords,b=this.faces,n=this.matrix;var l=this.aboutx,j=this.abouty,g=this.aboutz,d=this.offx,c=this.offy,a=this.offz;var r=n[0],o=n[1],m=n[2];for(var q=0,s=p.length;q<s;q++){k=p[q].x+l;h=p[q].y+j;f=p[q].z+g;e[q].x=(r[0]*k)+(r[1]*h)+(r[2]*f)+d;e[q].y=(o[0]*k)+(o[1]*h)+(o[2]*f)+c;e[q].z=(m[0]*k)+(m[1]*h)+(m[2]*f)+a}for(var q=0,s=b.length,t;q<s;q++){t=b[q].normal;k=t.x;h=t.y;f=t.z;b[q].worldnormal=new Vector3D((r[0]*k)+(r[1]*h)+(r[2]*f),(o[0]*k)+(o[1]*h)+(o[2]*f),(m[0]*k)+(m[1]*h)+(m[2]*f))}},transformToScreen:function(){var k,g,e,a=this.worldcoords,h=this.screencoords,m=this.screenx,l=this.screeny,d=this.perslevel;this.rminx=this.rminy=4096;this.rmaxx=this.rmaxy=-4096;for(var b=0,j,f,c=this.points.length;b<c;b++){k=a[b].x;g=a[b].y;e=a[b].z+d;if(e===0){e=0.001}j=h[b].x=((k*d)/e)+m;f=h[b].y=l-((g*d)/e);if(j<this.rminx){this.rminx=j}if(j>this.rmaxx){this.rmaxx=j}if(f<this.rminy){this.rminy=f}if(f>this.rmaxy){this.rmaxy=f}}},executePipeline:function(){if(this.recalculateNormals){var c=this.faces,m=this.points;for(var g=0,e=c.length,h,b,l,f,a,k,d;g<e;g++){h=c[g].vertices;b=m[h[1]].x-m[h[0]].x;l=m[h[1]].y-m[h[0]].y;f=m[h[1]].z-m[h[0]].z;a=m[h[2]].x-m[h[0]].x;k=m[h[2]].y-m[h[0]].y;d=m[h[2]].z-m[h[0]].z;c[g].normal=calcNormalVector(b,l,f,a,k,d)}}K3D.K3DObject.superclass.executePipeline.call(this);this.transformToScreen();this.controller.getRenderer(this.drawmode).sortByDistance(this)},executeRenderer:function(a){this.controller.getRenderer(this.drawmode).renderObject(this,a)},calculateAverageZ:function(){var d=0,c=this.worldcoords;for(var b=0,a=this.points.length;b<a;b++){d+=c[b].z}this.averagez=d/this.points.length}})})();(function(){K3D.LightSource=function(b,c,a){K3D.LightSource.superclass.constructor.call(this);this.location=b;this.color=c;this.intensity=a;return this};extend(K3D.LightSource,K3D.BaseObject,{color:null,intensity:null,location:null,worldvector:null,transformToWorld:function(){var b=this.matrix;var a=this.location.x+this.aboutx;var d=this.location.y+this.abouty;var c=this.location.z+this.aboutz;this.worldvector=new Vector3D((b[0][0]*a)+(b[0][1]*d)+(b[0][2]*c)+this.offx,(b[1][0]*a)+(b[1][1]*d)+(b[1][2]*c)+this.offy,(b[2][0]*a)+(b[2][1]*d)+(b[2][2]*c)+this.offz)}})})();(function(){K3D.Renderer=function(){};K3D.Renderer.prototype={sortByDistance:function(a){},renderObject:function(b,a){}}})();(function(){K3D.PointRenderer=function(){K3D.PointRenderer.superclass.constructor.call(this);return this};extend(K3D.PointRenderer,K3D.Renderer,{sortByDistance:function(a){if(a.shademode!=="plain"&&a.sortmode==="sorted"){this.quickSortObject(a.screencoords,a.worldcoords,0,a.points.length-1)}},quickSortObject:function(h,c,i,d){var e=i,g=d,f;var b;if(d>i){f=c[(i+d)>>1].z/2;while(e<=g){while(e<d&&c[e].z>f){e++}while(g>i&&c[g].z<f){g--}if(e<=g){b=h[e];h[e]=h[g];h[g]=b;b=c[e];c[e]=c[g];c[g]=b;e++;g--}}if(i<g){this.quickSortObject(h,c,i,g)}if(e<d){this.quickSortObject(h,c,e,d)}}},renderObject:function(f,m){var b,h,k;var j=f.screencoords,d=f.worldcoords,n=f.depthscale,a=n/128,l=f.linescale/255;for(var e=0,g=f.points.length;e<g;e++){h=d[e].z+n;h=h/a;switch(f.shademode){case"lightsource":case"plain":m.fillStyle="rgb("+f.color[0]+","+f.color[1]+","+f.color[2]+")";break;case"depthcue":if(h<0){h=0}else{if(h>255){h=255}}h=255-Ceil(h);m.fillStyle=f.depthcueColors[h];break}k=l*h;if(k<0.1){k=0.1}f.outputlinescale=k;m.beginPath();m.arc(j[e].x,j[e].y,k,0,TWOPI,true);m.closePath();m.fill()}}})})();(function(){K3D.WireframeRenderer=function(){K3D.WireframeRenderer.superclass.constructor.call(this);return this};extend(K3D.WireframeRenderer,K3D.Renderer,{sortByDistance:function(a){if(a.shademode!=="plain"&&a.sortmode==="sorted"){this.quickSortObject(a.worldcoords,a.edges,0,a.edges.length-1)}},quickSortObject:function(i,b,h,c){var d=h,g=c,e;var f;if(c>h){e=((i[(b[(h+c)>>1].a)].z)+(i[(b[(h+c)>>1].b)].z))/2;while(d<=g){while((d<c)&&((i[(b[d].a)].z+i[(b[d].b)].z)/2>e)){d++}while((g>h)&&((i[(b[g].a)].z+i[(b[g].b)].z)/2<e)){g--}if(d<=g){f=b[d];b[d]=b[g];b[g]=f;d++;g--}}if(h<g){this.quickSortObject(i,b,h,g)}if(d<c){this.quickSortObject(i,b,d,c)}}},renderObject:function(h,q){var k,n,m,o;var f=h.edges,l=h.screencoords,e=h.worldcoords;var r=h.depthscale,d=r/128,p=h.linescale/255;q.lineWidth=h.linescale;for(var g=0,j=f.length;g<j;g++){n=f[g].a;m=f[g].b;switch(h.shademode){case"lightsource":case"plain":k=h.color;q.strokeStyle="rgb("+k[0]+","+k[1]+","+k[2]+")";break;case"depthcue":k=((e[n].z+e[m].z)/2)+r;k=k/d;if(k<0){k=0}else{if(k>255){k=255}}k=255-Ceil(k);q.strokeStyle=h.depthcueColors[k];o=p*k;if(o<0.1){o=0.1}h.outputlinescale=o;q.lineWidth=o;break}q.beginPath();q.moveTo(l[n].x,l[n].y);q.lineTo(l[m].x,l[m].y);q.closePath();q.stroke()}}})})();(function(){K3D.SolidRenderer=function(){K3D.SolidRenderer.superclass.constructor.call(this);return this};extend(K3D.SolidRenderer,K3D.Renderer,{sortByDistance:function b(g){if(g.sortmode==="sorted"){this.quickSortObject(g.worldcoords,g.faces,0,g.faces.length-1)}},quickSortObject:function a(j,g,i,h){var l,k;g.sort(function(m,q){l=m.vertices;for(var n=0,p=0;n<l.length;n++){p+=j[l[n]].z}p=p/l.length;k=q.vertices;for(var n=0,o=0;n<k.length;n++){o+=j[k[n]].z}o=o/k.length;return(p<o?1:-1)})},renderObject:function c(y,D){var l=y.faces,m=y.screencoords,s=y.worldcoords;var p=y.depthscale,L=p/128;var z=new Vector3D(0,0,1);var u,A,H,K,J,x=PI/2,t,N=y.controller.lights;var M=y.doublesided;var C=(1/y.perslevel)*256;if(y.fillmode==="fillstroke"){D.lineWidth=1}for(var B=0,G=l.length,w;B<G;B++){w=l[B];u=w.vertices;if(M||z.dot(w.worldnormal)<C){switch(y.shademode){case"plain":if(w.texture===null){J=w.color;t="rgb("+J[0]+","+J[1]+","+J[2]+")";this.renderPolygon(D,y,w,t)}else{this.renderPolygon(D,y,w)}break;case"depthcue":for(var F=0,E=u.length,v=0;F<E;F++){v+=s[u[F]].z}var q=((v/u.length)+p)/L;if(q<0){q=0}else{if(q>255){q=255}}if(w.texture===null){q=(255-q)/255;J=w.color;A=Ceil(q*J[0]);H=Ceil(q*J[1]);K=Ceil(q*J[2]);t="rgb("+A+","+H+","+K+")"}else{q=255-Ceil(q);t="rgba(0,0,0,"+(1-(q/255))+")"}this.renderPolygon(D,y,w,t);break;case"lightsource":if(N.length===0){var I=z.thetaTo2(w.worldnormal);J=w.color;A=Ceil(I*(J[0]/PI));H=Ceil(I*(J[1]/PI));K=Ceil(I*(J[2]/PI));if(w.texture===null){t="rgb("+A+","+H+","+K+")"}else{t="rgba(0,0,0,"+(1-I*ONEOPI)+")"}this.renderPolygon(D,y,w,t)}else{A=H=K=0;for(var F=0,E=N.length,o,h;F<E;F++){o=N[F];I=PI-o.worldvector.thetaTo2(w.worldnormal);h=I*((1/o.worldvector.distance(w.worldnormal))*o.intensity)/PI;A+=(h*o.color[0]);H+=(h*o.color[1]);K+=(h*o.color[2])}if(A>1){A=1}if(H>1){H=1}if(K>1){K=1}J=w.color;var k=Ceil(A*J[0])+","+Ceil(H*J[1])+","+Ceil(K*J[2]);if(w.texture===null){t="rgb("+k+")"}else{t="rgba("+k+","+(1-(A+H+K)*0.33333)+")"}this.renderPolygon(D,y,w,t)}break}}}},renderPolygon:function d(t,l,o,s){var p=l.screencoords,n=o.vertices;t.save();if(o.texture===null){if(l.fillmode==="inflate"){var g=this.inflatePolygon(n,p);t.beginPath();t.moveTo(g[0].x,g[0].y);for(var m=1,h=n.length;m<h;m++){t.lineTo(g[m].x,g[m].y)}t.closePath()}else{t.beginPath();t.moveTo(p[n[0]].x,p[n[0]].y);for(var m=1,h=n.length;m<h;m++){t.lineTo(p[n[m]].x,p[n[m]].y)}t.closePath()}switch(l.fillmode){case"fill":t.fillStyle=s;t.fill();break;case"filltwice":t.fillStyle=s;t.fill();t.fill();break;case"inflate":t.fillStyle=s;t.fill();break;case"fillstroke":t.fillStyle=s;t.fill();t.strokeStyle=s;t.stroke();break;case"hiddenline":t.strokeStyle=s;t.stroke();break}}else{var q=l.textures[o.texture];var k=function(G,D,L,C,J,A,H){var B=this.inflatePolygon(G,p);t.save();t.beginPath();t.moveTo(B[0].x,B[0].y);for(var K=1,I=G.length;K<I;K++){t.lineTo(B[K].x,B[K].y)}t.closePath();t.clip();var O=B[0].x,x=B[0].y,N=B[1].x,w=B[1].y,M=B[2].x,v=B[2].y;var u=u=1/(D*(H-J)-C*H+A*J+(C-A)*L);var Q=-(L*(M-N)-J*M+H*N+(J-H)*O)*u,P=(J*v+L*(w-v)-H*w+(H-J)*x)*u,z=(D*(M-N)-C*M+A*N+(C-A)*O)*u,y=-(C*v+D*(w-v)-A*w+(A-C)*x)*u,F=(D*(H*N-J*M)+L*(C*M-A*N)+(A*J-C*H)*O)*u,E=(D*(H*w-J*v)+L*(C*v-A*w)+(A*J-C*H)*x)*u;t.transform(Q,P,z,y,F,E);t.drawImage(q,0,0);if(s){t.fillStyle=s;t.fill()}t.restore()};k.call(this,n.slice(0,3),0,0,q.width,0,q.width,q.height);if(n.length===4){var r=[];r.push(n[2]);r.push(n[3]);r.push(n[0]);k.call(this,r,q.width,q.height,0,q.height,0,0)}}t.restore()},inflatePolygon:function e(p,r){var s=[],l=new Array(p.length);for(var n=0,m=p.length,h,t,g,q,v,u,o;n<m;n++){h=r[p[n]].x;t=r[p[n]].y;if(n<m-1){g=r[p[n+1]].x;q=r[p[n+1]].y}else{g=r[p[0]].x;q=r[p[0]].y}v=q-t;u=-(g-h);o=Sqrt(v*v+u*u);v/=o;u/=o;v*=0.5;u*=0.5;s.push({x:h+v,y:t+u});s.push({x:g+v,y:q+u})}for(var n=0,m=p.length,k;n<m;n++){if(n===0){k=this.intersection(s[(m-1)*2],s[(m-1)*2+1],s[0],s[1])}else{k=this.intersection(s[(n-1)*2],s[(n-1)*2+1],s[n*2],s[n*2+1])}if(Abs(k.x-r[p[n]].x)>1.5||Abs(k.y-r[p[n]].y)>1.5){k.x=r[p[n]].x;k.y=r[p[n]].y}l[n]=k}return l},intersection:function f(j,i,n,m){var h,p,l,g,o,k,q;h=i.x-j.x;p=n.x-m.x;l=n.x-j.x;g=i.y-j.y;o=n.y-m.y;k=n.y-j.y;q=(p*k-o*l)/(g*p-h*o);return{x:j.x+q*(i.x-j.x),y:j.y+q*(i.y-j.y)}}})})();
\ No newline at end of file
deleted file mode 100644
--- a/testing/talos/talos/tests/canvasmark/scripts/mathlib-min.js
+++ /dev/null
@@ -1,1 +0,0 @@
-var RAD=Math.PI/180;var PI=Math.PI;var TWOPI=Math.PI*2;var ONEOPI=1/Math.PI;var PIO2=Math.PI/2;var PIO4=Math.PI/4;var PIO8=Math.PI/8;var PIO16=Math.PI/16;var PIO32=Math.PI/32;var Rnd=Math.random;var Sin=Math.sin;var Cos=Math.cos;var Sqrt=Math.sqrt;var Floor=Math.floor;var Atan2=Math.atan2;var Ceil=Math.ceil;var Abs=Math.abs;function randomInt(a,b){return ~~(Rnd()*(b-a+1)+a)}function weightedRandom(b){var a=Rnd();if(a<0.5){return 1-Math.pow(1-a,b!==undefined?b:2)/2}return 0.5+Math.pow((a-0.5)*2,b!==undefined?b:2)/2}function calcNormalVector(b,d,f,a,c,e){return new Vector3D((d*e)-(f*c),-((e*b)-(a*f)),(b*c)-(d*a)).norm()}function extend(d,e,c){var b=function(){},a;b.prototype=e.prototype;d.prototype=new b();d.prototype.constructor=d;d.superclass=e.prototype;if(e.prototype.constructor==Object.prototype.constructor){e.prototype.constructor=e}if(c){for(a in c){if(c.hasOwnProperty(a)){d.prototype[a]=c[a]}}}}function isArray(a){return(a.constructor.toString().indexOf("Array")!==-1)}(function(){Vector=function(x,y){this.x=x;this.y=y;return this};Vector.prototype={x:0,y:0,clone:function(){return new Vector(this.x,this.y)},set:function(v){this.x=v.x;this.y=v.y;return this},add:function(v){this.x+=v.x;this.y+=v.y;return this},nadd:function(v){return new Vector(this.x+v.x,this.y+v.y)},sub:function(v){this.x-=v.x;this.y-=v.y;return this},nsub:function(v){return new Vector(this.x-v.x,this.y-v.y)},dot:function(v){return this.x*v.x+this.y*v.y},length:function(){return Sqrt(this.x*this.x+this.y*this.y)},distance:function(v){var xx=this.x-v.x,yy=this.y-v.y;return Sqrt(xx*xx+yy*yy)},theta:function(){return Atan2(this.y,this.x)},thetaTo:function(vec){var v=this.clone().norm(),w=vec.clone().norm();return Math.acos(v.dot(w))},thetaTo2:function(vec){return Atan2(vec.y,vec.x)-Atan2(this.y,this.x)},norm:function(){var len=this.length();this.x/=len;this.y/=len;return this},nnorm:function(){var len=this.length();return new Vector(this.x/len,this.y/len)},rotate:function(a){var ca=Cos(a),sa=Sin(a);with(this){var rx=x*ca-y*sa,ry=x*sa+y*ca;x=rx;y=ry}return this},nrotate:function(a){var ca=Cos(a),sa=Sin(a);return new Vector(this.x*ca-this.y*sa,this.x*sa+this.y*ca)},invert:function(){this.x=-this.x;this.y=-this.y;return this},ninvert:function(){return new Vector(-this.x,-this.y)},scale:function(s){this.x*=s;this.y*=s;return this},nscale:function(s){return new Vector(this.x*s,this.y*s)},scaleTo:function(s){var len=s/this.length();this.x*=len;this.y*=len;return this},nscaleTo:function(s){var len=s/this.length();return new Vector(this.x*len,this.y*len)}}})();(function(){Vector3D=function(a,c,b){this.x=a;this.y=c;this.z=b;return this};Vector3D.prototype={x:0,y:0,z:0,clone:function(){return new Vector3D(this.x,this.y,this.z)},set:function(a){this.x=a.x;this.y=a.y;this.z=a.z;return this},add:function(a){this.x+=a.x;this.y+=a.y;this.z+=a.z;return this},sub:function(a){this.x-=a.x;this.y-=a.y;this.z-=a.z;return this},dot:function(a){return this.x*a.x+this.y*a.y+this.z*a.z},cross:function(a){return new Vector3D(this.y*a.z-this.z*a.y,this.z*a.x-this.x*a.z,this.x*a.y-this.y*a.x)},length:function(){return Sqrt(this.x*this.x+this.y*this.y+this.z*this.z)},distance:function(b){var c=this.x-b.x,d=this.y-b.y,a=this.z-b.z;return Sqrt(c*c+d*d+a*a)},thetaTo:function(b){var a=this.y*b.z-this.z*b.y,d=this.z*b.x-this.x*b.z,c=this.x*b.y-this.y*b.x;return Atan2(Sqrt(a*a+d*d+c*c),this.dot(b))},thetaTo2:function(a){return Math.acos(this.dot(a)/(Sqrt(this.x*this.x+this.y*this.y+this.z*this.z)*Sqrt(a.x*a.x+a.y*a.y+a.z*a.z)))},norm:function(){var a=this.length();this.x/=a;this.y/=a;this.z/=a;return this},scale:function(a){this.x*=a;this.y*=a;this.z*=a;return this}}})();(function(){Preloader=function(){this.images=new Array();return this};Preloader.prototype={images:null,callback:null,counter:0,addImage:function b(c,d){var e=this;c.url=d;c.onload=function(){e.counter++;if(e.counter===e.images.length){e.callback.call(e)}};this.images.push(c)},onLoadCallback:function a(e){this.counter=0;this.callback=e;for(var d=0,c=this.images.length;d<c;d++){this.images[d].src=this.images[d].url}}}})();
\ No newline at end of file
--- a/testing/talos/talos/unittests/test_config.py
+++ b/testing/talos/talos/unittests/test_config.py
@@ -225,17 +225,16 @@ class Test_get_config(object):
         cls.argv_tp5n = '--activeTests tp5n -e /some/random/path'.split()
         cls.argv_tp5o = '--activeTests tp5o -e /some/random/path'.split()
         cls.argv_tp5o_webext = '--activeTests tp5o_webext -e /some/random/path'.split()
         cls.argv_tp5o_scroll = '--activeTests tp5o_scroll -e /some/random/path'.split()
         cls.argv_v8_7 = '--activeTests v8_7 -e /some/random/path'.split()
         cls.argv_kraken = '--activeTests kraken -e /some/random/path'.split()
         cls.argv_basic_compositor_video = \
             '--activeTests basic_compositor_video -e /some/random/path'.split()
-        cls.argv_tcanvasmark = '--activeTests tcanvasmark -e /some/random/path'.split()
         cls.argv_dromaeo_css = '--activeTests dromaeo_css -e /some/random/path'.split()
         cls.argv_dromaeo_dom = '--activeTests dromaeo_dom -e /some/random/path'.split()
         cls.argv_tsvgm = '--activeTests tsvgm -e /some/random/path'.split()
         cls.argv_tsvgx = '--activeTests tsvgx -e /some/random/path'.split()
         cls.argv_tsvg_static = '--activeTests tsvg_static -e /some/random/path'.split()
         cls.argv_tsvgr_opacity = '--activeTests tsvgr_opacity -e /some/random/path'.split()
         cls.argv_tscrollx = '--activeTests tscrollx -e /some/random/path'.split()
         cls.argv_a11yr = '--activeTests a11yr -e /some/random/path'.split()
@@ -764,37 +763,16 @@ class Test_get_config(object):
             'docshell.event_starvation_delay_hint': 1,
             'full-screen-api.warning.timeout': 500,
             'media.ruin-av-sync.enabled': True
         }
         assert test_config['filters'] is not None
         assert test_config['unit'] == 'ms/frame'
         assert test_config['lower_is_better'] is True
 
-    def test_tcanvasmark_has_expected_attributes(self):
-        config = get_config(self.argv_tcanvasmark)
-        test_config = config['tests'][0]
-
-        assert test_config['name'] == 'tcanvasmark'
-        assert test_config['tpmanifest'] != '${talos}/tests/canvasmark/canvasmark.manifest'
-        assert 'win_counters' not in test_config
-        assert 'w7_counters' not in test_config
-        assert 'linux_counters' not in test_config
-        assert 'mac_counters' not in test_config
-        assert test_config['tpcycles'] == 5
-        assert test_config['tppagecycles'] == 1
-        assert test_config['timeout'] == 900
-        assert test_config['gecko_profile_interval'] == 10
-        assert test_config['gecko_profile_entries'] == 2500000
-        assert test_config['tpmozafterpaint'] is False
-        assert test_config['preferences'] == {'dom.send_after_paint_to_content': False}
-        assert test_config['filters'] is not None
-        assert test_config['unit'] == 'score'
-        assert test_config['lower_is_better'] is False
-
     def test_dromaeo_css_has_expected_attributes(self):
         config = get_config(self.argv_dromaeo_css)
         test_config = config['tests'][0]
 
         assert test_config['name'] == 'dromaeo_css'
         assert test_config['tpcycles'] == 1
         assert test_config['filters'] is not None
         assert test_config['lower_is_better'] is False
--- a/tools/rewriting/ThirdPartyPaths.txt
+++ b/tools/rewriting/ThirdPartyPaths.txt
@@ -60,17 +60,16 @@ netwerk/sctp/src/
 netwerk/srtp/src/
 nsprpub/
 other-licenses/
 parser/expat/
 security/nss/
 security/sandbox/chromium/
 testing/gtest/gmock/
 testing/gtest/gtest/
-testing/talos/talos/tests/canvasmark/
 testing/talos/talos/tests/dromaeo/
 testing/talos/talos/tests/kraken/
 testing/talos/talos/tests/v8_7/
 third_party/aom/
 third_party/python/blessings/
 third_party/python/configobj/
 third_party/python/futures/
 third_party/python/jsmin/