Source: rat.js

/**
 * @license
 *
 * Rat Game Engine
 *
 * Copyright 2013-2017 Wahoo Studios, Inc. and Steven H. Taylor.
 *
 * See the Rat home page for release versions and license information:
 * http://www.wahoo.com/rat/
 *
 */
 
//-----------------------------------------------------------------------------------------------------------
//	r_base
//
// The base module has the bare minimum to start loading rat.
// Most of the rest of rat is in other modules.
//
// Unfortunately, this r_base module is a lot of stuff shoved together.
//		We wanted to reference just one boot rat module in an html file and have all the other modules load as needed,
//		but we ended up needing a lot of base functionality to load and debug correctly, so this got complicated.
//		We could switch to a code-building system (like typescript?), but so far we've avoided that.
// 

/**                                                             
   Set up a bunch of core rat elements that we need to start everything else up
 */

// WinJS support
if (typeof (WinJS) !== "undefined")
	window.WinJS.Binding.optimizeBindingReferences = true;

/**                                                             
    Define the core RAT engine object
    
    @export 
    @namespace
   
 */
var rat = rat || {};
rat.loaded = false;  // Is rat loaded yet?

/**  Get the global app
    Rat uses this method to get the global app objects (if it exists) in some rare cases.
    Internally, rat uses this to pass the app object to code module load functions, in case it's needed,
   	but I suspect nobody uses this.
 */
rat.getGlobalApp = function ()
{
	if (typeof (app) !== "undefined")
		return app;
	return void 0;
};

/**                                                             
    rat.console (debugging, logging, etc.)
    this is the bare minimum rat.console support in r_base.  The rest is defined in r_console
    @namespace
 */
rat.console = {
	output: [],
	saveOutputToLocalStorage: false, // Save all log entries to local storage
	copyToSystem: true,
	logRemotely: false,
	remoteURL: false,
	maxLogLines: 150,
	onceRecord: {},

	lastText: "",
	repeat: 0,
	
	logNewlines: function(text)
	{
		if( !text )	return;
		text = text.toString();
		
		var strings = text.split( "\n" );
		var count = strings.length;
		//	suppress trailing blank lines, e.g. if the string ended in a \n and we just got a trailing ""
		while (strings[count-1] === "")
			count--;
		
		for( var index = 0; index < count; ++index )
		{
			rat.console.log( strings[index] );
		}
	},

	//	log text
	log: function (text)
	{
		var rconsole = rat.console;
		var out = rconsole.output;

		if (text === rconsole.lastText)
		{
			rconsole.repeat = rconsole.repeat + 1;
			out[out.length - 1] = "|(X" + rconsole.repeat + ") " + rconsole.lastText;
		}
		else
		{
			rconsole.lastText = text;
			rconsole.repeat = 1;
			out.push('| ' + text);
			if (out.length > rconsole.maxLogLines)
				out.splice(0, 1);	//	kill oldest
		}

		if( rat.console.saveOutputToLocalStorage && rat.system.has.localStorage )
			rat.system.getLocalStorage().setItem("RAT_LOG", JSON.stringify(out));
		
		//	copy to standard output?
		if (rconsole.copyToSystem)
		{
		    var data = '|' + text;
			console.log('|' + text);
			if( rat.system.has.xboxLE || rat.system.has.xbox )
			    Debug.writeln(data);
		}

		//	send to a remote server?
		if (rconsole.logRemotely && rconsole.remoteURL)
		{
			var xmlhttp = new XMLHttpRequest();
			//xmlhttp.onreadystatechange=function(){};
			xmlhttp.open("GET", rconsole.remoteURL + text, true);
			xmlhttp.send();
		}
	}
};

/**                                                             
    initial rat.system object, e.g. platform capabilities
    @namespace
 */
rat.system = {
	has: {},		//  system supported feature list
	onPlatform: "",	// What platform are we on.
	applyCacheBuster: false // When loading, include a cache buster.
};
rat.events = { queued: {} };

/** 
    detect platform and capabilities
    this accesses variables to determine if they exist, but this throws warnings in JSLint.
    @suppress {undefinedVars | missingProperties} - Don't warn about the Windows variable or the XboxJS variable
   
 */
rat.detectPlatform = function ()
{
	if (rat.detectPlatform.detected)
		return;

	rat.detectPlatform.detected = true;
	rat.console.log("rat detecting platform...");
	rat.system.has.pointer = true;	// assume this for starters.  This means mouse or finger.
	if (typeof Windows !== 'undefined' && typeof winJS !== 'undefined')
	{
		rat.system.has.windows8 = true;
		rat.system.has.winJS = true;
		//var isWinJS = !!window.Windows && /^ms-appx:/.test(location.href);

		if (window.Windows.Xbox !== void 0)
		{
			rat.system.has.xbox = true;
			rat.system.has.pointer = false; // sure, it could be Kinect, but we don't support that at all right now.
		}
		else
		{
			rat.system.has.realWindows8 = true;
		}
		//	Note:  Detecting Windows 8.1 (vs. windows 8.0) at runtime is evidently nontrivial.
		//	If you build a win8.0 app, everybody reports everything the same as 8.0 even if you're actually hosted in 8.1.
	}
	else if (typeof XboxJS !== 'undefined')
	{
		rat.system.has.xboxLE = true;
		rat.system.has.pointer = false;		// should be true when we support Kinect
	}

	// Are we running in wraith?
	if (typeof Wraith !== 'undefined' && Wraith && Wraith.w_isNative)
	{
		rat.system.has.Wraith = true;
		rat.system.onPlatform = Wraith.w_onPlatform;
	}

	//	browser/navigator/host stuff
	var nav = navigator;

	//	Detect support for the gamepad API
	//	Reference: http://www.html5rocks.com/en/tutorials/doodles/gamepad/
	rat.system.has.gamepadAPI =
		!!nav.getGamepads ||
		!!nav.gamepads ||
		!!nav.mozGetGamepads ||
		!!nav.mozGamepads ||
		!!nav.webkitGetGamepads ||
		!!nav.webkitGamepads;
	if (rat.system.has.gamepadAPI && rat.system.has.xboxLE)
		rat.system.has.gamepadAPI = false;
	//	Does our host support the event driven gamepad API?  Note that Wraith does not.
	rat.system.has.gamepadAPIEvent = rat.system.has.gamepadAPI && (navigator.userAgent.indexOf('Firefox/') !== -1) && !rat.system.has.Wraith;
	//rat.console.log("Gamepad API Support: " + rat.system.has.gamepadAPI + "  gamepadAPIEvents:" + rat.system.has.gamepadAPIEvent);

	//	iOS Browser
	if (nav && (nav.userAgent.search("iPad") >= 0 || nav.userAgent.search("iPod") >= 0 || nav.userAgent.search("iPhone") >= 0))
		rat.system.has.iOSBrowser = true;
	else
		rat.system.has.iOSBrowser = false;

	//	PS4 Browser
	if (nav && (nav.userAgent.search("PlayStation 4") >= 0))
		rat.system.has.PS4Browser = true;
	else
		rat.system.has.PS4Browser = false;

	//	Chrome browser
	if (nav && (nav.userAgent.search("Chrome") >= 0))
	{
		rat.system.has.chromeBrowser = true;
		var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
		rat.system.has.chromeVersion = raw ? parseInt(raw[2], 10) : 0;
	}
	else
		rat.system.has.chromeBrowser = false;

	//	IE browser
	//	see http://www.useragentstring.com/pages/Internet%20Explorer/
	if (nav && ((nav.userAgent.search("MSIE") >= 0) || (nav.userAgent.search("Edge") >= 0)) )
	{
		rat.system.has.IEBrowser = true;
		
		//	todo: convert edge/* to version
		//	(see chrome version check above)
		if (nav && (nav.userAgent.search("Edge/12")) >= 0)
			rat.system.has.IEVersion = 12;
		else if (nav && (nav.userAgent.search("like Gecko") >= 0))
			rat.system.has.IEVersion = 11;
		else if (nav && (nav.userAgent.search("MSIE 10") >= 0))
			rat.system.has.IEVersion = 10;
		else if (nav && (nav.userAgent.search("MSIE 9") >= 0))
			rat.system.has.IEVersion = 9;
		else
			rat.system.has.IEVersion = 0;	//	unknown
	}
	else
		rat.system.has.IEBrowser = false;

	if (nav && nav.userAgent.indexOf('Firefox/') !== -1)
		rat.system.has.firefoxBrowser = true;
	else
		rat.system.has.firefoxBrowser = false;

	if (nav && nav.userAgent.indexOf('Gecko/') !== -1)	//	a more generic form of firefox
		rat.system.has.geckoBrowser = true;
	else
		rat.system.has.geckoBrowser = false;

	if (typeof(chrome) !== "undefined" && chrome.app && chrome.app.runtime)
	{
		//	note: there's some interesting stuff in chrome.runtime, like chrome.runtime.PlatformOs
		rat.system.has.chromeApp = true;
	}
	
	//	CPU class stuff
	if (nav && nav.cpuClass)	//	ARM, x64, others..
		rat.system.has.cpuClass = nav.cpuClass;
	else
		rat.system.has.cpuClass = 'unknown';
	
	//	Library detection
	if (typeof(QUnit) !== "undefined" )
	{
		rat.system.has.QUnit = true;
		rat.system.has.unitTest = true;
	}

	//	hosting environment - electron (which will also report chrome stuff above)
	rat.system.has.electron = !!(window.process && window.process.versions && window.process.versions['electron']);
	rat.system.has.electronVersion = rat.system.has.electron ? (window.process.versions['electron']) : 0;

	//	local storage detection.
	//	Attempting to access the global variable window.localStorage in the Edge Browser,
	//	at least in file:// mode, will throw an exception.  So, let's deal with that once, here.
	//	In general, everyone should be using rat.storage objects,
	//	but that system itself and a few other systems (like the rat console) want to access localStorage directly,
	//	so let's set up this check now.
	//	This should be the only place we ever try to access window.localStorage (or localStorage as a global)
	var storage;
	try {
		storage = window.localStorage;
	} catch (exception) {
		storage = null;
	}
	if (storage)
	{
		rat.system.has.localStorage = true;
		rat.system.localStorageObject = storage;
	} else {
		rat.system.has.localStorage = false;
		rat.system.localStorageObject = null;
	}
	
};
rat.system.getLocalStorage = function () {return rat.system.localStorageObject;};

/**                                                             
  	Parse the URL to get standard argument vars out
 */
(function(rat)
{
	//only if we actually have window.location. This is to avoid errors in Wraith where window.location is undefined
	if (window.location){
		var loc = window.location.href;
		
		//	don't include extra stuff in base URL
		var extraIndex = loc.indexOf("?");
		if (extraIndex > 0)
			rat.system.baseURL = loc.slice(0, extraIndex);
		else
			rat.system.baseURL = loc;

		//	Pull values out of the URL		
		rat.system.URLVars = {};
		loc.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) {
			rat.system.URLVars[key] = value; //returns parts
		});
	}
})(rat);

/**                                                             
    For assets (e.g. images) that are pulled from an external server we may need to
    	fully qualify our path before sending out the request.
   	This is also a very useful bottleneck function for cachebusting, fixing xbox LE paths,
   		and applying other global asset path overrides for our own convenience.
    @suppress {missingProperties}
 */
rat.system.fixPath = function (urlPath)
{
	// don't do anything if the path was already empty to begin with
	if (urlPath.length === 0)
		return urlPath;

	var isProperAddress = urlPath.search(/^http/) > -1;		// we want to exclude any requests that are already properly formed

	// project is running a landing experience, prepend with the base URL and randomness to break up caching
	var newPath = urlPath;
	if( !isProperAddress )
	{
		//	Here are a bunch of path modifications that rat supports:
		
		//	project is running from a subfolder, e.g. for the intelXDK to keep crap out of top level.
		if (rat.system.rootFolder)
		{
			newPath = rat.system.rootFolder + newPath;
			if (rat.utils.cleanPath)
				newPath = rat.utils.cleanPath(newPath);
		}
		//	check for rat itself being in an alternate path (separate from everything else)
		if (rat.system.ratAltFolder && newPath.substring(0,4) === "rat/")
			newPath = rat.system.ratAltFolder + newPath.substring(4);
		//	rootSubFolder is just a boolean flag that says go up one directory
		if (rat.system.rootSubFolder)
			newPath = "../" + newPath;
		//	cache buster support (append random number so host caching is avoided, and newest version of file is always loaded)
		if (rat.system.has.xboxLE || rat.system.applyCacheBuster)
			newPath = newPath + "?_=" + Math.random();
		//	in real LE environment, load from correct hosted path
		if (rat.system.has.xboxLE)
			newPath = window.adParams._projectBase + newPath;
	}
	return newPath;
};

/**                                                             
   We need to add this event right now so we can detect when the app is activated
 */
if (window.WinJS !== void 0)
{
	var activation;
	if (window.Windows && window.Windows.ApplicationModel)
		var activation = window.Windows.ApplicationModel.Activation;
	if (!activation)
		activation = {};
	if (!activation.ActivationKind)
		activation.ActivationKind = {};
	if (!activation.ApplicationExecutionState)
		activation.ApplicationExecutionState = {};

	window.WinJS.Application.onactivated = function (event)
	{
		var eventArgs = {};
		//	see https://msdn.microsoft.com/en-us/library/ie/windows.applicationmodel.activation.activationkind
		switch (event.detail.kind)
		{
			case activation.ActivationKind.launch: eventArgs.kind = "launched"; break;
			case activation.ActivationKind.search: eventArgs.kind = "searchWith"; break;
			case activation.ActivationKind.shareTarget: eventArgs.kind = "shareTarget"; break;
				// Launched via file association
			case activation.ActivationKind.file: eventArgs.kind = "file"; break;
			case activation.ActivationKind.protocol: eventArgs.kind = "protocol"; break;
			case activation.ActivationKind.fileOpenPicker: eventArgs.kind = "fileOpenPicker"; break;
			case activation.ActivationKind.fileSavePicker: eventArgs.kind = "fileSavePicker"; break;
			case activation.ActivationKind.cachedFileUpdater: eventArgs.kind = "cachedFileUpdater"; break;
			case activation.ActivationKind.ContactPicker: eventArgs.kind = "contactPicker"; break;
			case activation.ActivationKind.device: eventArgs.kind = "autoplay"; break;
			case activation.ActivationKind.printTaskSettings: eventArgs.kind = "print"; break;
			case activation.ActivationKind.cameraSettings: eventArgs.kind = "camera"; break;
			case activation.ActivationKind.restrictedLaunch: eventArgs.kind = "launched-restricted"; break;
			case activation.ActivationKind.appointmentsProvider: eventArgs.kind = "appointmentsProvider"; break;
			case activation.ActivationKind.Contact: eventArgs.kind = "contact"; break;
			case activation.ActivationKind.lockScreenCall: eventArgs.kind = "launch-locked"; break;
			default:
				eventArgs.kind = "unknown";
				break;
		}

		// The previous app state
		// https://msdn.microsoft.com/en-us/library/ie/windows.applicationmodel.activation.applicationexecutionstate
		switch (event.detail.previousExecutionState)
		{
			case activation.ApplicationExecutionState.notRunning: eventArgs.prevState = "notRunning"; break;
			case activation.ApplicationExecutionState.running: eventArgs.prevState = "running"; break;
			case activation.ApplicationExecutionState.suspended: eventArgs.prevState = "suspended"; break;
				// Terminated after a suspend.
			case activation.ApplicationExecutionState.terminated: eventArgs.prevState = "terminated"; break;
				// Closed by the user
			case activation.ApplicationExecutionState.closedByUser: eventArgs.prevState = "closed"; break;
			default:
				eventArgs.prevState = "unknown";
				break;
		}

		// Get the session data if there is any
		eventArgs.sessionState = (window.WinJS.Application.sessionState || {})["savedState"];

		// win8 version of starting up - do this after WinJS.UI.processAll has been called
		// for one thing, this lets us access any weird win8 specific UI
		// for another it means the above code has been run, which lets us access session data.
		event.setPromise(window.WinJS.UI.processAll().then(function ()
		{
			//	Queue up the activate event to be fired to the first listener
			//	Also queue a resume from terminate event if we were terminated
			//	Have we loaded the events module
			rat.events.queued["activated"] = ["activated", eventArgs];
			if (eventArgs.prevState === "terminated")
				rat.events.queued["resumeFromTerminated"] = ["resumeFromTerminated", eventArgs];
		}));
	};

	window.WinJS.Application.start();
}
	//	If we don't have WinJS, always queue the activated event
else
{
	rat.events.queued["activated"] = ["activated", {
		kind: "launched",
		prevState: "notRunning",
		sessionState: void 0
	}];
}

/**                                                             
   Provides requestAnimationFrame/cancelAnimationFrame in a cross-browser way.
 */
window.requestAnimationFrame =
	window.requestAnimationFrame ||
	window.webkitRequestAnimationFrame ||
	window.mozRequestAnimationFrame ||
	window.oRequestAnimationFrame ||
	window.msRequestAnimationFrame ||
	function ( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element)
	{
		return window.setTimeout(callback, 1000 / 60);
	};
window.cancelAnimationFrame =
	window.cancelAnimationFrame ||
	window.webkitCancelAnimationFrame ||
	window.mozCancelAnimationFrame ||
	window.oCancelAnimationFrame ||
	window.msCancelAnimationFrame ||
	clearTimeout; // The params of cancelAnimationFrame and clearTimeout match, so I don't need a wrapper around this function

/**                                                             
    Util functions necessary for loading rat
    @namespace 
 */
rat.utils = {
	// List of all JS files that are either queued to load, loading, or loaded
	// Key is full file path
	// value is either "pending", "downloading", "loaded" or "error"
	// Pending is we are going to call loadScriptWithCallback
	// Downloading is loadScriptWithCallback has been called..  File is downloading.
	// Loaded is file is downloaded
	// Error is a failure
	loadedScripts: {
		length: 0
	},
};

(function (rat)
{
	/**  Format an exception for output to the log
 */
	rat.utils.dumpException = function (msg, e) {
		if (!e)
			rat.console.log("Unable to dump undefined exception");
		else {
			rat.console.log("=======================================================");
			rat.console.log("ERROR!  " + msg );
			rat.console.log("->   Got Exception " + e.name + "(" + (e.number || "???") + ")");
			rat.console.log("->   " + e.description || e.message);
			if (e.stack && typeof(e.stack) === "string")
				rat.console.log("->   " + e.stack);

			delete e.message;
			delete e.description;
			delete e.name;
			delete e.number;
			var fullText = JSON.stringify(e);
			var lines = fullText.split("\\n");
			for (var index = 0; index < lines.length; ++index) {
				rat.console.log("->" + lines[index]);
			}
		}
	};

	/** 
	    Load in a list of resources (JS, JSON, XML(Not yet supported)), with a callback when they are all completed
	    @param {?} options 
 */
	rat.utils.loadResources = function (list, callback, options)
	{
		var resourceList = list;
		if (!Array.isArray(resourceList))
			resourceList = [resourceList];
		options = options || {};
		if (options.async === void 0)
			options.async = true;
		var updateCB = options.update;
		var totalLoads = resourceList.length;
		var loadsLeft = resourceList.length;

		//	Fix the types in the list
		var index;
		var len;
		for (index = 0, len = resourceList.length; index !== len; ++index)
		{
			//	Entries in the resourceList may be raw strings..  change them to objects
			if (typeof (resourceList[index]) === 'string')
				resourceList[index] = { source: resourceList[index] };
			resourceList[index].loaded = false;
		}

		if (updateCB)
			updateCB({ left: totalLoads, loaded: 0, of: totalLoads });
		if (loadsLeft <= 0)
		{
			if (callback)
				callback();
			return;
		}

		/**  Callback used by loadResource
		    defined in this function so it can access the variables like loadsLeft
 */
		function resourceLoadDone(obj, data)
		{
			if (obj)
			{
				if (obj.loaded)
					return;
				obj.loaded = true;
				if (obj.callback)
					obj.callback(data);
			}

			--loadsLeft;
			//rat.console.log("...loaded " + obj.source + ".  Loads left:" + loadsLeft);
			if (updateCB)
				updateCB({ left: loadsLeft, loaded: (totalLoads - loadsLeft), of: totalLoads });
			if (loadsLeft <= 0 && callback)
				callback();
		}

		var entry;
		for (index = 0, len = resourceList.length; index !== len; ++index)
		{
			//	Entries in the resourceList may be raw strings..  change them to objects
			entry = resourceList[index];
			//	If the objects type is not set, get it from the resource ext.
			if (entry.type === void 0)
			{
				var dotAt = entry.source.lastIndexOf(".");
				if (dotAt === -1)
					entry.type = "UNKNOWN";
				else
					entry.type = entry.source.substr(dotAt + 1).toUpperCase();
			}

			if (entry.type === "JS" && !rat.utils.loadedScripts[entry.source])
			{
				rat.utils.loadedScripts[entry.source] = "pending";
				++rat.utils.loadedScripts.length;
			}
		}

		for (index = 0, len = resourceList.length; index !== len; ++index)
		{
			entry = resourceList[index];

			//	Process based on type
			//	rat.console.log( "...loading " + entry.source + "..." );
			switch (entry.type)
			{
				// Load Javascript
				case 'JS':
					// All JS loads need to go through loadScriptWithCallback
					rat.utils.loadScriptWithCallback(entry.source, !!options.async, resourceLoadDone.bind(void 0, entry));
					break;

					// Load JSON
				case 'JSON':
					if (!options.async)
						rat.console.log("ERROR!  Unable to load JSON files synchronously");
					else
						rat.utils.loadJSON(entry.source, resourceLoadDone.bind(void 0, entry));
					break;

				default:
					rat.console.log("WARNING! Attempting to load unrecognized resource " + entry.source);
					resourceLoadDone(entry, void 0);
					break;
			}
		}
	};

	/** 
	    Load in a series of files (or file sets) in a synchronous manner.  This will load one set and then the next.
	    This differs from rat.utils.loadResources in that each file (or file set) is downloaded and processed one at a time
	    @param {Object=} options 
 */
	rat.utils.loadResourcesSync = function (list, callback, options)
	{
		if (typeof (list) === "string")
			list = [list];
		list = list || [];

		var onIndex = -1; // What index of item are we on.
		options = options || {};
		function loadNextList()
		{
			++onIndex;

			if (options.update)
			{
				//	How to stop this load process 
				var abort = options.update({ index: onIndex, list: list, startAgain: loadNextList });
				if (abort)
					return;
			}

			//	When to stop.
			if (onIndex >= list.length)
			{
				if (callback)
					callback();
				return;
			}

			var entry = list[onIndex];
			if (options.verbose)
				rat.console.log("Sync load of list item " + onIndex + ":" + JSON.stringify(entry));

			rat.utils.loadResources(entry, loadNextList, { async: false });
		}

		loadNextList();
	};

	/** 
	    load one script with this callback
	    (generally, you want to use loadResources() above)
	    @param {?} errorCallback 
 */
	rat.utils.loadScriptWithCallback = function (filename, async, completeCallback, errorCallback)
	{
		//	Report of we ever try to re-load a scripts
		if (rat.utils.loadedScripts[filename] !== void 0 && rat.utils.loadedScripts[filename] !== "pending")
		{
			rat.console.log("WARNING:  File " + filename + " is getting loaded twice!");
			filename += Math.random();
		}
		if (!rat.utils.loadedScripts[filename])
			++rat.utils.loadedScripts.length;
		rat.utils.loadedScripts[filename] = "downloading";

		//rat.console.log("load script with callback " + filename + ", " + async);
		//	Fix the path
		var src = rat.system.fixPath(filename);

		//	When we are done, call this
		function loadDone(obj)
		{
			//rat.console.log( "Script file " + filename + " loaded." );
			// Is this is load event or did we get a ready-state change to complete.
			if (obj && (obj.type === "load" || obj.readyState === "complete"))
			{
				//rat.console.log( "Script file " + filename + " state " + obj.type + " has cb " + (!!completeCallback));
				rat.utils.loadedScripts[filename] = "loaded";
				if (completeCallback)
					completeCallback(filename);
			}
		}
		// console.log("starting wraith load for " + filename);

		// Load in the script (Using wraith if we can)
		if (rat.system.has.Wraith)
		{
			//rat.console.log( "Loading script " + src + " with wraith:" + (async ? "Async" : "Sync") );
			Wraith.LoadScript({
				src: src,
				async: async,
				complete: loadDone,
			});
		}
		else
		{
			//rat.console.log( "Loading script " + src + " with DOM:" + (async ? "Async" : "Sync") );

			//	Create a new script tag
			var script = document.createElement('script');

			//	Set it up
			script.async = async || false;
			script.type = "text/javascript";
			script.onreadystateChange = loadDone;
			script.onload = loadDone;
			script.onerror = function ()
			{
				rat.utils.loadedScripts[filename] = "error";
				rat.console.log("Failed to load file " + src);
				if (errorCallback)
					errorCallback(filename);
			};
			script.src = src;

			//	Add it to the document
			var docHead = document.getElementsByTagName('head')[0];
			docHead.appendChild(script);
			//rat.console.log( "Added script tag for " + src );
		}
	};
	
	//	Load in the QUnit testing framework.
	rat.utils.loadQUnit = function(onDone)
	{
		function continueIfDone()
		{
			if( !haveCSS || !haveJS )
				return;
			var canvas = document.getElementsByTagName("canvas")[0];
			//canvas.style.postion = "absolute";
			var body = document.getElementsByTagName("body")[0];
			body.style.overflow = "visible";
			function createDiv(id){
				var div = document.createElement("div");
				div.id = id;
				//div.style.position = "absolute";
				body.insertBefore( div, canvas );
			}
			createDiv( "qunit" );
			createDiv( "qunit-fixture" );
			
			onDone();
		}
			
		var haveCSS = false, haveJS = false;
		
		//	Load the QUnit JS file
		rat.utils.loadScriptWithCallback( "http://code.jquery.com/qunit/qunit-1.20.0.js", true, function(){
			haveJS = true;
			continueIfDone();
		});
		
		//	Load the QUnit CSS file
		var fileref=document.createElement("link");
		fileref.setAttribute("rel", "stylesheet");
		fileref.setAttribute("type", "text/css");
		fileref.onload = function() {
			haveCSS = true;
			continueIfDone();
		};
		fileref.setAttribute("href", "http://code.jquery.com/qunit/qunit-1.20.0.css");
		document.getElementsByTagName("head")[0].appendChild(fileref);
	};
})(rat);

/**                                                             
   rat.load (module management)
 */
(function (rat)
{
	/** 
	    @namespace
 */
	rat.modules = {};

	// Rats full modules list
	rat.modules.byName = {}; // Key is module name
	rat.modules.length = 0;
	rat.modules.pending = []; // Array version of any of the above that we have not yet processed

	/**                                                             
 */
	rat.modules.moduleFileLookup = {};
	rat.modules.moduleRequiresList = {};
	rat.modules.addModulesToFileLookup = function (obj)
	{
		for (var name in obj)
		{
			if (obj.hasOwnProperty(name))
			{
				if (rat.modules.moduleFileLookup[name] && rat.modules.moduleFileLookup[name] !== obj[name])
					rat.console.log("Module name " + name + " duplicated. in file lookup for " + rat.modules.moduleFileLookup[name] + "/" + obj[name]);
				else
					rat.modules.moduleFileLookup[name] = obj[name];
			}
		}
	};
	rat.modules.addModulesToRequiresLookup = function (obj)
	{
		for (var name in obj)
		{
			if (obj.hasOwnProperty(name))
			{
				if (rat.modules.moduleRequiresList[name])
					rat.console.log("Module name " + name + " duplicated. in requires lookup");
				else
					rat.modules.moduleRequiresList[name] = obj[name];
			}
		}
	};

	/**  This is a list of files that need to be loaded in response to modules that have been added
 */
	rat.modules.missingFiles = {
		list: [],
		byPath: {}
	};

	//
	// Convert a module name to a path
	function moduleNameToPath(module)
	{
		module = module.replace(/\./g, "/");
		module = module + ".js";
		//module = module.toLowerCase();
		return module;
	}
	
	//
	//	Convert a path to a module name.
	//	Used in limited cases.  We generally don't want to assume a file has only one module.
	function pathToModuleName(path)
	{
		var dotPos = path.lastIndexOf('.');
		if (dotPos > -1)
			path = path.substring(0, dotPos);
		path = path.replace(/\//g, ".");
		//path = path.toLowerCase();
		return path;
	}

	//
	//	Check a platform requirement
	//	This is done in a flexible way by looking to see if this platform name is set in rat.system.has
	//
	rat.modules.checkPlatformDep = function(platform)
	{
		if (!platform || !platform.length)
			return true;

		else if (platform[0] == "!")
			return rat.system.has[platform.substring(1)] == void 0;
		
		else if (rat.system.has[platform])
			return true;

		return false;
	}


	//
	//	Add a file that needs to be loaded before we can continue processing
	function addModuleMissingFiles(module, ops)
	{
		var out_fileList = [];
		var index;
		var foundFiles;

		//	If this is an array of modules, 
		if (Array.isArray(module))
		{
			for (index = 0; index !== module.length; ++index)
			{
				foundFiles = addModuleMissingFiles(module[index], ops);
				out_fileList = out_fileList.concat(foundFiles);
			}
			return out_fileList;
		}

		//	We only care about the modules name
		//	This is also where we respect platform restructions
		if (typeof (module) === "object")
		{
			if (!rat.modules.checkPlatformDep(module.platform))
				return [];
			module = module.name;
		}

		//	Is this a file and not a module?
		var file;
		var isFile = module.substr(-3).toLowerCase() === ".js";
		if (isFile)
		{
			//	Before we go queueing up this load, let's see if the module is already in our list.
			var expectedModule = pathToModuleName(module);
			if (rat.modules.byName[expectedModule])
				return [];
			//	fine - let's load that file.
			file = module;
		}
		else
		{
			//	Does the module already exist?  Don't need to load any file
			if (rat.modules.byName[module])
				return [];

			//	If it isn't a file, try to find the file it is in..
			file = rat.modules.moduleFileLookup[module];

			//	If we don't have a lookup for it, then build the file path from the module name
			if (!file)
				file = moduleNameToPath(module);
		}

		// Is it already a pending load, or already loaded.
		// We assume that if this is the case, that we don't need to find their dependencies...
		var lcFile = file.toLowerCase();
		if (!rat.modules.missingFiles.byPath[lcFile] && !rat.utils.loadedScripts[file])
		{
			if (!ops || !ops.justGenFileList)
			{
				rat.modules.missingFiles.byPath[lcFile] = true;
				rat.modules.missingFiles.list[rat.modules.missingFiles.list.length] = lcFile;
			}
			out_fileList[out_fileList.length] = file;

			//	Now handle getting other files that we know we will need (see moduleRequiresList)
			var lookup = rat.modules.moduleRequiresList[module];
			if (lookup && lookup.length)
			{
				foundFiles = addModuleMissingFiles(lookup, ops);
				out_fileList = out_fileList.concat(foundFiles);
			}
		}
		return file;
	}

	//
	//	Get the list of modules required by another module.
	//	NOTE: This is NOT recursive.  We only go one level deep
	//		It also takes into account platform restrictions
	//		This can take either a module name, or a moduleOps object
	//	Returned is a list of module names.
	function getModuleRequires(moduleName, ops)
	{
		if (!moduleName)
			return [];
		var list;
		var module = moduleName;
		ops = ops || {};
		if (typeof (moduleName) === "string")
			module = rat.modules.byName[moduleName];
		list = module.requires;
		var out_list = [];
		//	From the full list, build the applicable list
		for (var index = 0; index !== list.length; ++index)
		{
			var entry = list[index];
			//	is this a platform that we don't care about
			if (!rat.modules.checkPlatformDep(entry.platform))
				continue;
			if (ops.forFiles || !entry.fileOnly)
				out_list[out_list.length] = entry.name;
		}
		return out_list;
	}

	//	Modules that rat will ALWAYS load
	var requiredModules = [
		"rat.os.r_system",
		"rat.os.r_events",
		"rat.debug.r_console",
		"rat.debug.r_trackvalue",	//	really needed?
		"rat.debug.r_profiler",		//	really needed?
		"rat.utils.r_utils",
		"rat.math.r_math",
		"rat.math.r_vector",
		"rat.math.r_matrix",
		{ name: "rat.test.r_qunit", platform: "QUnit" }
	];

	//
	//	Register a new rat modules
	//
	rat.modules.add = function (name, requires, code)
	{
		// Extract param..  We support two different types of calls
		// Pure param (name, requires, code)
		// and Named param ({name:"", requires:[], code:function(){}});
		// @TODO Add an initFunc phase so different systems can register their own init functions
		var ops, index;
		if (typeof (name) !== "object")
		{
			ops = {
				name: name,
				requires: requires,
				code: code
			};
		}
		else
			ops = name;

		// Validation
		if (!ops.name)
		{
			rat.console.log("ERROR: Modules must be given a name");
			ops.name = "_" + ((Math.random() * 10000) | 0);
		}

		// Flag that we still need to execute any affiliated code
		// NOTE: Even if no code is provided, this will not get set
		// Until all required modules have loaded
		ops.hasBeenProcessed = false;

		//	Cleanup the requires list
		if (!ops.requires)
			ops.requires = [];
		else if (!Array.isArray(ops.requires))
			ops.requires = [ops.requires];
		for (index = 0; index !== ops.requires.length; ++index)
		{
			var entry = ops.requires[index];
			if (!entry.name)
			{
				//	Default behavior is file only dependency
				entry = {
					name: entry,
					platform: void 0,
					processBefore: false,
					fileOnly: true
				};
			}

			//	If we have the processBefore flag, set fileOnly to be the reverse of the flag
			if (entry.processBefore !== void 0)
				entry.fileOnly = !entry.processBefore;
				//	If we done have the processBefore flag, but do have the fileOnly, processBefore is the reverse of fileOnly
			else if (entry.fileOnly !== void 0)
				entry.processBefore = !entry.fileOnly;
			else
			{
				entry.fileOnly = true;
				entry.processBefore = false;
			}
			//entry.name = entry.name.toLowerCase(),
			ops.requires[index] = entry;
		}

		// does this module already exist?
		if (rat.modules.byName[ops.name])
		{
			rat.console.log("ERROR: Attempting to re-define module " + ops.name);
			return;
		}

		// Add the module to the defined list.
		rat.modules.byName[ops.name] = ops;
		rat.modules.pending[rat.modules.pending.length] = ops;
		++rat.modules.length; // Inc total number of modules

		// queue any dependent JS files that are not loaded (or getting loaded)
		var list = getModuleRequires(ops, { forFiles: true });
		addModuleMissingFiles(list);
		
		//	Remove this modules file from the missing files list
		//	We do this to avoid attempting to re-load files that are part of a concatenated file
		var myFile = moduleNameToPath(ops.name).toLowerCase();
		var missingFileIndex = rat.modules.missingFiles.list.indexOf( myFile );
		if( missingFileIndex >= 0 )
		{
			rat.modules.missingFiles.list.splice( missingFileIndex, 1 );
			delete rat.modules.missingFiles.byPath[myFile];
		}
	};

	//
	// Return if a given module is ready to be processed
	rat.modules.isReadyToProcess = function (moduleOps, outList)
	{
		if (!moduleOps)
			return false;
		outList = outList || [];
		var modules = rat.modules.byName;
		var module = moduleOps;
		if (!module)
		{
			rat.console.log("Testing if undefined module is ready to process");
			return false;
		}
		//	If we have already processed this module, then the answer is no.
		if (module.hasBeenProcessed)
			return false;

		//	test all required modules
		var list = getModuleRequires(module);
		for (var index = 0; index !== list.length; ++index)
		{
			var depOn = list[index];
			depOn = modules[depOn];
			if (depOn && depOn.fileOnly)
				continue;
			if (!depOn)
				outList.push({ name: list[index], hasBeenProcessed: false, isMissing: true });
			else if (!depOn.hasBeenProcessed)
				outList.push(depOn);
		}

		//	Must be all good
		return outList.length === 0;
	};

	//
	//	Execute all modules in the order specified by their requirements
	rat.modules.process = function (ops)
	{
		ops = ops || {};
		var maxToCall = ops.maxToCall || 0;
		var noError = !!ops.noError;

		//	Keep looping until we have no more pending modules.
		//	NOTE: By the time we reach this code, all required files should be loaded.
		var pending = rat.modules.pending;
		while (pending.length > 0)
		{
			//	Loop back->front because we will be removing items from the list
			++rat.modules.process.passes;
			var index = pending.length - 1;
			var oldPending = pending.length;
			while (index >= 0)
			{
				//	If this module is ready, process it.
				var module = pending[index];
				if (rat.modules.isReadyToProcess(module))
				{
					if (ops.verbose)
						rat.console.log("rat module processing : " + module.name);
					//	Execute the code if it exists
					//	Pass rat, and if it exists, the app object.
					if (module.code) {
						try {
							module.code(rat, rat.getGlobalApp());
						} catch (e) {
							//	ERROR processing the module.
							//	This often means a syntax error in the module code.
							//	To debug this, look at the value of the "e" variable
							rat.utils.dumpException("Failed to process module " + module.name + ".", e);
							throw e;
						}
					}
					if (ops.verbose)
						rat.console.log("done with : " + module.name);

					//	Flag processed
					module.hasBeenProcessed = true;

					//	Remove from the pending list.
					pending.splice(index, 1);

					//	We may only want to call so many items per frame
					//	to avoid a large FPS spike
					//	Currently, only wraith does this.
					if (maxToCall > 0 && pending.length > 0)
					{
						--maxToCall;
						if (maxToCall <= 0)
							return "HIT_LIMIT";
					}
				}
				//	Loop backwards
				--index;
			}

			//	If the number of pending modules did not change, we have a problem
			if (pending.length === oldPending)
			{
				if (!noError)
				{
					rat.console.log("ERROR! BAD REQUIRES TREE!  Unable to process " + pending.length + " modules.  (circular or missing dependencies?)");
					for (index = 0; index !== pending.length; ++index)
					{
						rat.console.log(">" + pending[index].name);
						var list = [];
						rat.modules.isReadyToProcess(pending[index], list);
						for (var reqIndex = 0; reqIndex !== list.length; ++reqIndex)
							rat.console.log("  >" + list[reqIndex].name + (list[reqIndex].isMissing ? " MISSING!" : ""));
					}
					return "ERROR";
				}
				else
					return "PENDING";
			}
		}
	};
	rat.modules.process.passes = 0;

	//
	//	Load the rat engine by listing the modules that will be used.
	//	Modules are defined by using rat.modules.add (see above)
	rat.load = function (loadOptions)
	{
		loadOptions = loadOptions || {};
		
		rat.console.log("rat.load Verbose=" + !!loadOptions.verbose);
		rat.load.numLoadPasses = 0; // How many load passes have we done so far

		var loadLog = function (text)
		{
			if (loadOptions.verbose)
				rat.console.log(text);
		};

		if (loadOptions.async !== void 0 && !loadOptions.async)
			loadOptions.async = false;
		else
			loadOptions.async = true;

		// loadOptions.update = function
		// loadOptions.done = function
		var addAsync = loadOptions.addAsync || [];
		var addSync = loadOptions.addSync || [];
		var fileLists = [addAsync, addSync];

		// Start by detecting the platform
		rat.detectPlatform();

		// Adjust all of the module lists to the same format
		for (var fileListIndex = 0; fileListIndex !== fileLists.length; ++fileListIndex)
		{
			var singleList = fileLists[fileListIndex];
			for (var fileIndex = singleList.length - 1; fileIndex >= 0; --fileIndex)
			{
				if (!singleList[fileIndex])
					singleList.splice(fileIndex, 1);
				else if (!singleList[fileIndex].name)
					singleList[fileIndex] = { name: singleList[fileIndex] };
			}
		}

		// Find out if we have already loaded the wraith_core file
		// Note: wraith_core may be loaded in situations where the Wraith engine is not being used.
		var loadedWraithCore = typeof (Wraith) === "object";

		// If we are not in wraith, but we want wraith files loaded, then add the required files
		if (!rat.system.has.Wraith && rat.load.addWraith)
		{
			var wraithPath = "rat/wraithjs/web/";
			if (!loadOptions.skipLoad)
			{
				if (!loadedWraithCore)
					addModuleMissingFiles(wraithPath + "wraith_core.js");
				addModuleMissingFiles([wraithPath + "wraith_dom.js", wraithPath + "wraith_canvas.js"]);
			}
		}

		var loadsLeft, loadsDone;
		var framesOfSameState = 0;
		var lastFramesState = void 0;
		var fileLoadDone = false;
		var fileProcessDone = false;
		var requestAnimationFrameID;
		var syncStarted = false, syncDone = false;

		if (!loadOptions.skipLoad)
		{
			//	Add rat's required modules
			addModuleMissingFiles(requiredModules);

			//	Any modules listed in the addAsync get added now
			addModuleMissingFiles(addAsync);

			loadLog("Initial file pass selected the following " + rat.modules.missingFiles.list.length + " files to load:");
			for (var index = 0; index < rat.modules.missingFiles.list.length; ++index)
				loadLog("-->" + rat.modules.missingFiles.list[index]);
		}
		else
		{
			//	Avoid all other loads.  NOT PROCESS calls
			loadsLeft = 0;
			loadsDone = 0;
			fileLoadDone = true;
			syncStarted = true;
			syncDone = true;
		}

		// Update while loading/processing files
		function update()
		{
			if (rat.load.failed)
				return;
			var percent;
			++framesOfSameState;

			//	Still loading files
			if (!fileLoadDone)
				percent = (loadsDone / rat.utils.loadedScripts.length) * 100;

				//	@TODO The file load may be done, but we may need to do another loading pass for files that we are missing

				//	Done loading files, but still processing
			else if (!fileProcessDone)
			{
				var maxToCall = (rat.system.has.Wraith && loadOptions.async) ? 1 : 0;
				var processRes = rat.modules.process({ maxToCall: maxToCall, verbose:loadOptions.verbose }); // Max to process per frame
				if (processRes === "ERROR")
				{
					rat.load.failed = true;
					return;
				}
				if (rat.modules.pending.length <= 0)
				{
					fileProcessDone = true;
					lastFramesState = void 0;
					loadLog("rat.modules.process finished.  Took " + rat.modules.process.passes + " passes.");
				}

				percent = ((rat.modules.length - rat.modules.pending.length) / rat.modules.length) * 100;
			}

			//	Keep track of the number of frames nothing has changed.
			if (percent !== lastFramesState)
			{
				lastFramesState = percent;
				framesOfSameState = 0;
			}

			//	IF we have too many frames with no change, report what we are waiting on
			if (framesOfSameState >= 6000)	//	was 60, was way too spewy...
			{
				framesOfSameState = 0;
				if (fileLoadDone)
					rat.console.log("How did we go \"idle\" while trying to process files?!?  Modules left to proccess: " + rat.modules.pending.length);
				else
				{
					rat.console.log("Went \"idle\" waiting for " + loadsLeft + " of " + rat.utils.loadedScripts.length + " file(s) to load.");
					for (var file in rat.utils.loadedScripts)
					{
						if (rat.utils.loadedScripts[file] === "pending" || rat.utils.loadedScripts[file] === "downloading")
							rat.console.log("--->" + file);
					}
				}
			}

			//	Fire the update callback if one was provided
			if (loadOptions.update)
			{
				loadOptions.update({
					load: {
						done: loadsDone,
						left: loadsLeft,
						total: rat.utils.loadedScripts.length,
						isDone: fileLoadDone
					},
					process: {
						done: rat.modules.length - rat.modules.pending.length,
						left: rat.modules.pending.length,
						total: rat.modules.length,
						isDone: fileProcessDone
					},
				});
			}

			//	Are we ready to move onto the sync phase (if we need to)?
			if (fileLoadDone && fileProcessDone)
			{
				//	Are we ready to start the sync load pass?
				if (!syncStarted)
				{
					syncStarted = true;
					if (!addSync.length)
						syncDone = true;
					else
					{
						loadLog("Starting sync load of " + addSync.length + " files.");
						var fileList = addModuleMissingFiles(addSync, { justGenFileList: true });
						var singleFileLoaded = function (fields)
						{
							if (rat.modules.missingFiles.list.length > 0)
							{
								if (!singleFileLoaded.paused)
								{
									singleFileLoaded.paused = true;
									rat.console.log("Pause sync loading after " + (fields.index) + " of " + fields.list.length + " files...");
								}
								else
									rat.console.log("Keep sync loading paused.  More missing files...");
								loadMissingFiles(function ()
								{
									singleFileLoaded(fields);
								}, { logTag: "paused sync" });
								return "abort";
							}
							else
							{
								rat.modules.process({ noError: true });
								if (singleFileLoaded.paused)
								{
									rat.console.log("Resume sync loading " + (fields.list.length - (fields.index + 1)) + " of " + fields.list.length + " files...");
									singleFileLoaded.paused = false;
									fields.startAgain();
								}
								return false;
							}
						};
						singleFileLoaded.paused = false;
						rat.utils.loadResourcesSync(
							fileList,
							function () { syncDone = true; },
							{ update: singleFileLoaded, verbose: loadOptions.verbose }
						);
					}
				}

				// Is everything loaded and processed
				if (syncDone)
				{
					//	Make sure that we have no pending modules from the sync block
					rat.modules.process();
					rat.loaded = true;

					if (requestAnimationFrameID)
					{
						window.cancelAnimationFrame(requestAnimationFrameID);
						requestAnimationFrameID = 0;
					}
					if (loadOptions.done)
						loadOptions.done(rat, rat.getGlobalApp());
				}
				else
				{
					if (!requestAnimationFrameID)
						requestAnimationFrameID = window.requestAnimationFrame(updateFromAnimationFrame);
				}
			}
			else
			{
				if (!requestAnimationFrameID)
					requestAnimationFrameID = window.requestAnimationFrame(updateFromAnimationFrame);
			}
		}

		// This is how our regular update cycle happens in the module loading system
		function updateFromAnimationFrame()
		{
			requestAnimationFrameID = 0;
			update();
		}

		function loadMissingFiles(doneCB, ops)
		{
			ops = ops || {};
			ops.logTag = ops.logTag || "";
			++rat.load.numLoadPasses;
			var list = rat.modules.missingFiles.list;
			rat.modules.missingFiles.list = [];
			rat.modules.missingFiles.byPath = {};
			
			loadLog( "WARNING!  Another " + ops.logTag + " load phase (" + rat.load.numLoadPasses + ") is needed to get " + list.length + " required file(s)." );
			for (var index = 0; index !== list.length; ++index)
				loadLog("-->" + list[index]);
			rat.utils.loadResources(list, doneCB, {
				update: ops.update
			});
		}

		// Called when a file loading pass is done
		function loadFinished()
		{
			//	Once all files have been loaded, check if there are other files that we need to queue
			if (rat.modules.missingFiles.list.length > 0)
			{
				loadMissingFiles(loadFinished,
						  {
						  	update: function (fields)
						  	{
						  		loadsLeft = fields.left;
						  		loadsDone = rat.utils.loadedScripts.length - fields.left;
						  		update();
						  	}
						  });
			}
			else
			{
				loadLog("File load finished.  " + rat.modules.pending.length + " Modules to process.");

				fileLoadDone = true;

				//	If not async, run now.
				if (!loadOptions.async)
					update();
			}
		}

		var fileList = rat.modules.missingFiles.list;
		rat.modules.missingFiles.list = [];
		rat.modules.missingFiles.byPath = {};

		// Async load path
		if (!loadOptions.skipLoad)
		{
			if (loadOptions.async)
			{
				requestAnimationFrameID = window.requestAnimationFrame(updateFromAnimationFrame);

				loadLog("Starting async file load...");
				++rat.load.numLoadPasses;
				rat.utils.loadResources(fileList, loadFinished, {
					update: function (fields)
					{
						loadsLeft = fields.left;
						loadsDone = rat.utils.loadedScripts.length - fields.left;
						update();
					}
				});
				loadLog("... Load Started.");
			}
				//	Sync load path
			else
			{
				loadLog("Starting synchronous file load...");
				rat.utils.loadResources(fileList, loadFinished, { async: false });
			}
		}
		else
		{
			if (loadOptions.async)
				requestAnimationFrameID = window.requestAnimationFrame(updateFromAnimationFrame);
			loadFinished();
		}
	};

	/**  Sync loading utility wrapper
	    @param {?} doneFunc
	   
 */
	rat.simpleLoad = function (list, doneFunc)
	{
		rat.load({
			addSync: list,
			done: doneFunc
		});
	};
	rat.simpleload = rat.simpleLoad;	//	alt name

	/**  Set the entry point for the application
	    @param {Object=} context 
 */
	rat.load.setEntryPoint = function (func, context)
	{
		if (typeof (Wraith) === "object" && Wraith.SetEntryPoint)
			Wraith.SetEntryPoint(func, context);
		else
		{
			if (context)
				func = func.bind(context);
			window.onload = func;
		}
	};

	//	Allow an external function to call our entry point manually if it is needed
	rat.ExternalEntryPoint = function ()
	{
		if (window.onload)
			window.onload(null); //- This NULL is to fix google closure compiler linting errors.
	};

})(rat);

/**                                                 
   	Unit testing API
 */
(function(rat)
{
	//	Empty functions for unit testing
	rat.unitTest= {
		
		//	Are unit tests enabled.
		enabled: true,
		
		//	Create a test group
		//	platform specific code
		group: function(name, func) {},
		
		//	Define an actual test
		//	Platform genric.  Specific version is _test
		test: function(name, code) {
			//	only conintue if we are enabled and have a _test func
			if( rat.unitTest.enabled && rat.unitTest._test )
				rat.unitTest._test(name, code);
		},
		
		//	Utility to have a single test in a group
		testGroup: function( grpName, testName, testCode )
		{
			rat.unitTest.group( grpName, function(){
				rat.unitTest.test( testName, testCode );
			});
		}
	};
})(rat);

//-----------------------------------------------------------------------------------------------------------
//
//	r_minified
//
//	Used with rat.js so we know that RAT is minified 
//	Actually, this is a bit of a misnomer.  This flag means rat is concatenated and loaded in a
//	single load.  We don't know from this flag whether rat is minified, or how minified.
//

/**
 * Define that we are using a minified rat
 */
rat.system.has.minified = true;

//--------------------------------------------------------------------------------------------------
//
//	rat system-level functionality,
//	like main loop and update handling, system initialization order, 
//	setting up global update/draw/event functions, etc.
//
//	Note that a lot of system-level stuff is implemented in r_base
//
//	@todo	move some debug display functions to debug module
//
rat.modules.add( "rat.os.r_system",
[
	"rat.graphics.r_graphics",
	"rat.debug.r_console",
	"rat.debug.r_profiler",
	"rat.debug.r_trackvalue",	//	consider not depending on this in final releases somehow
	"rat.os.r_events",
	"rat.math.r_math",
	
	{ name: "rat.os.r_le_core", platform: "xboxLE" }, // Platform file
	{ name: "rat.os.r_electron", platform: "electron" } // electron utils
],
function(rat)
{
	rat.paused = false; // For Cheats
	
	//	todo move to "debug" subobject, and move to r_debug module
	//	and rename functions and variables = it's confusing how they conflict/contrast
	rat.system.debugDrawTiming = false;
	rat.system.debugDrawStats = false;
	rat.system.debugDrawFramerateGraph = false;
	rat.system.debugDrawMemoryGraph = false;
	rat.system.debugFramerateGraphSettings = {
		bounds: {}
	};
	rat.system.debugDrawConsole = false;

	rat.system.load = null;
	rat.system.commandHandler = null;
	rat.system.hasFocus = true;
	if (window.hasFocus)
		rat.system.hasFocus = window.hasFocus();
	else if (document.hasFocus)
		rat.system.hasFocus = document.hasFocus();
	rat.preLoadTimer = null;

	rat.mousePos = {x:-1, y:-1};	//	better way to mark whether we've ever actually gotten the mouse position from a move?

	/**
	* @param {string=} canvasID optional
	* @suppress {uselessCode} - rat.system.load may not exist.
	*/
	rat.init = function (canvasID, ops)
	{
		ops = ops || {};
		//	For max flexibility and robustness, support rat.init being called more than once,
		//	in which case just exit.
		if (rat.initialized)
		{
			//rat.console.log("rat already initialized.");
			return;
		}

		if( ops.useProtectedCycle )
		{
			var origCycle = rat.system.cycle;
			var hitError = false;
			rat.system.cycle = function()
			{
				try
				{
					origCycle();
				}
				catch( e )
				{
					if( !hitError )
					{
						hitError = true;
						rat.console.saveOutputToLocalStorage = true;
						var errStr = rat.utils.objectToString(e);
						rat.console.log( "!!!ERROR!!!" );
						rat.console.logNewlines( errStr );
					}
				}
			}
		}
		useProtectedCycle = !!ops.useProtectedCycle;
		
		rat.console.log("Initalizing Rat...");
		//rat.console.log("rs: " +rat.system);

		if(rat.system.load)
			rat.system.load();

		rat.detectPlatform();	//	detect platform and capabilities
		rat.profiler.init();
		rat.graphics.init(canvasID, ops.gfxInit);
		if( rat.particle )
			rat.particle.init();
		if( rat.audio )
			rat.audio.init();
		if (rat.clipboard)
			rat.clipboard.init();
		
		//	eventually, call cycle, which will set itself up repeatedly.
		//setInterval(rat.system.cycle, 1000 / 60);
		//setTimeout(rat.system.cycle, 1000 / 60);
		window.requestAnimationFrame(rat.system.cycle);

		if( rat.input )
		{
			rat.input.init();
			rat.input.autoHandleEvents();
		}
		
		rat.console.registerCommand("showMemory", function (cmd, args)
		{
			var on = rat.system.debugDrawMemoryGraph;
			rat.system.debugDrawMemoryGraph = !on;
		}, ["showMemory", "memory"]);
		
		rat.initialized = true;
	};

	rat.preLoadImages = function (imageList, postLoadFunction)
	{
		rat.postLoadFunction = postLoadFunction;
		rat.graphics.preLoadImages(imageList);	//	start the loading process
		rat.preLoadTimer = setInterval(rat.processPreLoad, 1000 / 60);	//	check back from time to time
	};

	rat.processPreLoad = function ()
	{
		if(rat.graphics.isCacheLoaded())
		{
			rat.console.log("rat preload done.");
			clearInterval(rat.preLoadTimer);
			rat.preLoadTimer = null;
			if(typeof rat.postLoadFunction !== 'undefined')
				rat.postLoadFunction();
		}
	};

	rat.setDraw = function (f)
	{
		rat.draw = f;
	};

	rat.setPostUIDraw = function (f)
	{
		rat.postUIDraw = f;
	};

	rat.setUpdate = function (f)
	{
		rat.update = f;
	};

	//
	//	main engine loop - do all updates and draw everything
	//
	rat.system.lastCycleTime = 0;
	rat.system.cycle = function ()
	{
		//	John:  This makes things harder for me when working in chrome - 
		//	I want chrome to give me the exception, not rat.  :)
		//	We could move all this code into a subroutine and then maybe optionally support a try/catch around it,
		//	based on platform or config or something?
		//try
		//{
			//	begin new frame
			rat.graphics.beginFrame();

			rat.profiler.pushPerfMark("Rat.System.Cycle");
			window.requestAnimationFrame(rat.system.cycle);	//	request next cycle immediately

			//	This is a system for capping framerate.  This avoids those super high spikes we get when the system
			//	suddenly gives us an update again very quickly.  Is this desirable?  I'm not sure.
			//if (0)
			//{
			//	var now = new Date().getTime();//Date().now();
			//	var elapsed = now - rat.system.lastCycleTime;

			//	var fpsInterval = rat.math.floor(1000/15);	//	1000/60 = 60 fps interval
			//	if (elapsed < fpsInterval)
			//		return;

			//	rat.system.lastCycleTime = now;// - (elapsed % fpsInterval);
			//}

			//	TODO:  move the above, and update deltatime into a new rat module, r_timing
			//		split timing loops into update and rendering?
			//		have updateDeltaTime tell us whether we've hit the minimum threshold for processing (to incorporate the above minimum check)

			

			//	update
			rat.profiler.pushPerfMark("updateDeltaTime");
			rat.system.updateDeltaTime();
			rat.profiler.popPerfMark("updateDeltaTime");

			if(rat.update)
			{
				rat.profiler.pushPerfMark("GameUpdate");
				rat.update(rat.deltaTime);
				rat.profiler.popPerfMark("GameUpdate");
			}

			if( rat.input )
			{
				rat.profiler.pushPerfMark("input.update");
				rat.input.update(rat.deltaTime);
				rat.profiler.popPerfMark("input.update");
			}

			if( rat.ui )
			{
				rat.profiler.pushPerfMark("ui.updateAnimators");
				rat.ui.updateAnimators(rat.deltaTime);
				rat.profiler.popPerfMark("ui.updateAnimators");
			}

			rat.profiler.pushPerfMark("updateScreens");
			rat.screenManager.updateScreens();
			rat.profiler.popPerfMark("updateScreens");
			
			if (rat.audio.update)
				rat.audio.update(rat.deltaTime);

			if(rat.cycleUpdate)
			{
				rat.profiler.pushPerfMark("cycleupdate");
				rat.cycleUpdate.updateAll(rat.deltaTime);
				rat.profiler.popPerfMark("cycleupdate");
			}
			
			if(rat.timerUpdater)
			{
				rat.profiler.pushPerfMark("timerUpdate");
				rat.timerUpdater.updateAll(rat.deltaTime);
				rat.profiler.popPerfMark("timerUpdate");
			}

			if (rat.Rumble && rat.Rumble.supported)
				rat.Rumble.frameUpdate(rat.deltaTime);

			//	draw, starting with global transform if any, and then installed draw routine
			var ctx = rat.graphics.getContext();
			rat.profiler.pushPerfMark("Rendering");
			rat.graphics.save();

			rat.graphics.setupMatrixForRendering();
			if(rat.graphics.autoClearCanvas)
			{
				rat.profiler.pushPerfMark("Clearing Canvas");
				rat.graphics.clearCanvas();
				rat.profiler.popPerfMark("Clearing Canvas");
			}

			if (rat.graphics.canvas.style && rat.graphics.canvas.style.backgroundImage && rat.system.has.Wraith)
			{
				rat.graphics.canvas.style.backgroundImage.drawTiled(rat.graphics.canvas.width, rat.graphics.canvas.height);
			}

			if (rat.beforeDraw)
			{
				rat.profiler.pushPerfMark("BEFORE draw");
				rat.beforeDraw(ctx);
				rat.profiler.popPerfMark("BEFORE draw");
			}
			
			if(rat.draw)
			{
				rat.profiler.pushPerfMark("rat.draw");
				rat.draw(ctx);
				rat.profiler.popPerfMark("rat.draw");
			}

			if( rat.screenManager )
			{
				rat.profiler.pushPerfMark("screen draw");
				rat.screenManager.drawScreens();
				rat.profiler.popPerfMark("screen draw");

				if (rat.postUIDraw)
				{
					rat.profiler.pushPerfMark("post UI draw");
					rat.postUIDraw(ctx);
					rat.profiler.popPerfMark("post UI draw");
				}
			}

			//	draw debug display, if it's turned on
			//	We used to do this after scale/translate, so we can be sure to be in the bottom corner...
			//	but that's lame.  For one thing, it makes it hard to see on high-res displays!
			//	Also, these variable names are ridiculous, but they're sort of embedded in many apps at this point.
			//	could at least rename the functions...  Or deprecate the old names but keep supporting them...?
			rat.profiler.pushPerfMark("Debug Rendering");
			rat.console.drawConsole();
			if (rat.trackValue)
				rat.trackValue.draw();
			if(rat.system.debugDrawTiming)
				rat.system.drawDebugTiming();
			if(rat.system.debugDrawStats)
				rat.system.drawDebugStats();
			if (rat.system.debugDrawFramerateGraph)
				rat.system.drawDebugFramerateGraph();
			if (rat.system.debugDrawMemoryGraph)
				rat.system.drawDebugMemoryGraph();
			if(rat.system.debugDrawConsole)
				rat.console.drawLog();
			if (rat.profiler && rat.profiler.displayStats)
			{
				rat.profiler.pushPerfMark("Profiler draw");
				rat.profiler.displayStats();
				rat.profiler.popPerfMark("Profiler draw");
			}
			rat.profiler.popPerfMark("Debug Rendering");

			rat.graphics.restore();	//	restore after scaling, translating, etc.
			rat.profiler.popPerfMark("Rendering");

			if (rat.afterDraw)
			{
				rat.profiler.pushPerfMark("After Draw");
				rat.afterDraw(ctx);
				rat.profiler.popPerfMark("After Draw");
			}
			
			rat.profiler.popPerfMark("Rat.System.Cycle");

			rat.graphics.endFrame();
		//}
		//catch (err)
		//{
		//	rat.console.log( "EXCEPTION: " + err.toString() );
		//}
	};
	
	//	timing globals - move this stuff to rat namespace
	rat.deltaTimeRaw = 1 / 60.0;	//	deltatime.  see calculation near framerate update
	rat.deltaTimeRawSmoothed = rat.deltaTimeRaw;
	rat.deltaTimeMod = 1;	//	a way to modify deltatime and speed things up
	rat.deltaTimeUnsmoothed = rat.deltaTime;
	rat.deltaTime = rat.deltaTimeRaw;
	rat.runningFpsUnsmoothed = 1 / rat.deltaTimeUnsmoothed;
	rat.runningFps = 1 / rat.deltaTime;
	rat.runningTime = 0;

	function getDeltaTimeStamp()
	{
		if (rat.system.has.Wraith)
			return 0;
		else if (window.performance)
			return window.performance.now()/1000;
		else
			return new Date().getTime()/1000;
	}

	var lastTimeStamp = getDeltaTimeStamp();
	//	This array is used to generate the smoothed deltaTimeRaw
	var gDeltaTimeRawSmoother = [];
	rat.SMOOTHING_SAMPLE_SIZE = 25;
	var gSmootherIndex = 0;

	//	These arrays are used by the FPS Debug graph
	var gDeltaTimeRawRecord = [];
	var gDeltaTimeRawSmoothedRecord = [];
	var FPS_RECORD_SIZE = 100;
	rat.system.FPS_RECORD_SIZE = FPS_RECORD_SIZE;
	var gFPSRecordIndex = 0;
	

	//	update deltatime calculation
	var gHighDeltaTime; // The highest delta time over the last amount of time.  This means the slowest FPS
	var gHighFor = 0;// How long as the high delta time been the hi
	rat.system.updateDeltaTime = function ()
	{
		//	Things break down with a delta time mode <= 0
		if (rat.deltaTimeMod <= 0)
			rat.deltaTimeMod = 0.0000000001;
		var now;

		//	Get the rat.deltaTimeRaw
		//	a bunch of timing stuff, both for debug display and for deltatime (DT) calculations
		//	todo: use higher precision timing.
		if (rat.system.has.Wraith)
			rat.deltaTimeRaw = Wraith.getDeltaTime();
		else
		{
			now = getDeltaTimeStamp();
			rat.deltaTimeRaw = now - lastTimeStamp;
			lastTimeStamp = now;
		}

		//	Force?
		if (rat.paused)
			rat.deltaTimeRaw = 0;

		//	artificial limit of dt for systems running so slowly that an accurate dt would be ridiculous.
		if (rat.deltaTimeRaw > 0.1 && !rat.paused)	//	10fps
			rat.deltaTimeRaw = 0.1;

		//	Include the deltaTimeMod in rat.deltaTime
		rat.deltaTimeUnsmoothed = rat.deltaTimeRaw * rat.deltaTimeMod;

		//	Get the raw FPS
		rat.runningFpsUnsmoothed = 1 / rat.deltaTimeUnsmoothed;

		//	Figure out the smoothed deltaTime
		//	this controls how quickly average frame rate matches immediate frame rate.  1 = just use current frame.  5 = average out over 5 frames.
		//	The value was 5 for a long time.  I'm just not sure that's right.
		var totalTimeRaw = 0;
		gDeltaTimeRawSmoother[gSmootherIndex] = rat.deltaTimeRaw;
		gSmootherIndex = (gSmootherIndex + 1) % rat.SMOOTHING_SAMPLE_SIZE;
		var recordSize = gDeltaTimeRawSmoother.length < rat.SMOOTHING_SAMPLE_SIZE ? gDeltaTimeRawSmoother.length : rat.SMOOTHING_SAMPLE_SIZE;
		for (var index = 0; index !== recordSize; ++index)
			totalTimeRaw += gDeltaTimeRawSmoother[index];
		rat.deltaTimeRawSmoothed = totalTimeRaw / recordSize;

		rat.deltaTime = rat.deltaTimeRawSmoothed * rat.deltaTimeMod;

		rat.runningFps = 1 / rat.deltaTime;

		gDeltaTimeRawRecord[gFPSRecordIndex] = rat.deltaTimeRaw;
		gDeltaTimeRawSmoothedRecord[gFPSRecordIndex] = rat.deltaTimeRawSmoothed;
		gFPSRecordIndex = (gFPSRecordIndex + 1) % FPS_RECORD_SIZE;
		rat.runningTime += rat.deltaTimeRaw;

		if (gHighDeltaTime === void 0 || (gHighFor += rat.deltaTimeRaw) > 2.0 || gHighDeltaTime < rat.deltaTimeRaw)
		{
			gHighDeltaTime = rat.deltaTimeRaw;
			gHighFor = 0;
		}
	};

	//	draw some debug timing info (framerate info)
	rat.system.drawDebugTiming = function ()
	{
		//var dispFps = rat.math.floor(rat.runningFps * 10)/10;
		var dispFps = rat.math.floor(rat.runningFps);
		var dispDT = rat.math.floor(rat.deltaTime * 1000) / 1000;
		var ctx = rat.graphics.getContext();
		ctx.fillStyle = "#F08030";
		var fontSize = rat.math.floor(12 * 1/rat.graphics.globalScale.x);
		ctx.font = "bold " + fontSize + "px monospace";
		var yPos = rat.graphics.SCREEN_HEIGHT - fontSize;
		ctx.fillText("fps: " + dispFps + "  DT: " + dispDT, 30, yPos);
	};

	//	draw debug stats - particle counts, for instance
	//	TODO: use offscreen for this and only update when it changes?
	//	fillText is slow and affects the performance we're trying to measure.  :(
	//	I'm hesitant to introduce a dependency here from rat.system to rat.ui or rat.offscreen
	//	do we already have a ui dependency?
	rat.system.drawDebugStats = function ()
	{
		var atX = 30;
		
		var ctx = rat.graphics.getContext();
		ctx.fillStyle = "#F08030";
		var fontSize = rat.math.floor(12 * 1/rat.graphics.globalScale.x);
		ctx.font = "bold " + fontSize + "px monospace";
		var yShift = fontSize;
		var yPos = rat.graphics.SCREEN_HEIGHT - 3 * fontSize;

		//	particle stats
		if( rat.particle )
		{
			var totalSystems = rat.particle.getSystemCount();
			var totalEmitters = rat.particle.getAllEmitterCount();
			var totalParticles = rat.particle.getAllParticleCount();
			var totalCachedStates = rat.particle.State.cacheSize;
			var totalParticleStateObjects = rat.particle.State.count;
			
			//	Particle system stats.
			if(rat.particle.stateCaching.enabled)
			{
				ctx.fillText("   states(cached): " + totalParticleStateObjects + "(" + totalCachedStates + ")", atX, yPos);
				yPos -= yShift;
			}
			ctx.fillText("P: sys: " + totalSystems + "  em: " + totalEmitters + "  p: " + totalParticles, atX, yPos);
			yPos -= yShift;
		}
		
		//	UI stats
		if (rat.graphics && rat.graphics.frameStats && rat.ui )
		{
			ctx.fillText("UI: elem: " + rat.graphics.frameStats.totalElementsDrawn
				+ ", mcalls " + rat.ui.mouseMoveCallCount
				+ ", ucalls " + rat.ui.updateCallCount,
				atX, yPos);
			yPos -= yShift;
		}
		
		//	image draw calls
		if (rat.graphics && rat.graphics.Image && rat.graphics.Image.perFrameDrawCount )
		{
			ctx.fillText("Images: " + rat.graphics.Image.perFrameDrawCount, atX, yPos);
			yPos -= yShift;
		}

		//	track XUI element render stats, if xui system is even supported
		if (rat.xuijs)
		{
			var total = 0;
			for( var key in rat.xuijs.XuiElementsDrawnThisFrame)
			{
				total += rat.xuijs.XuiElementsDrawnThisFrame[key];
				var skippedText = (
					(rat.xuijs.XuiElementsSkippedThisFrame[key])
					? " (/" + rat.xuijs.XuiElementsSkippedThisFrame[key] + ")"
					: "");
				ctx.fillText( "   "+ key +": "
					+ rat.xuijs.XuiElementsDrawnThisFrame[key]
					+ skippedText
					, atX, yPos );
				yPos -= yShift;
			}
			ctx.fillText( "XUI Elem: " + total, atX, yPos );
			yPos -= yShift;
			
			rat.xuijs.XuiElementsDrawnThisFrame = {};
			rat.xuijs.XuiElementsSkippedThisFrame = {};
		}
	};

	//	show debug memory graph (if we can)
	rat.system.drawDebugMemoryGraph = function ()
	{
		if( !rat.system.debugMemoryGraphSettings)
			rat.system.debugMemoryGraphSettings = {bounds:{}, history:[]};
		var settings = rat.system.debugMemoryGraphSettings;
		var ctx = rat.graphics.getContext();
		var space = {width : rat.graphics.SCREEN_WIDTH, height : rat.graphics.SCREEN_HEIGHT};
		if( !settings.bounds || !settings.bounds.x )
		{
			settings.bounds = {};
			settings.bounds.w = space.width/4;
			settings.bounds.h = space.height/8;
			settings.bounds.x = space.width * 0.90 - settings.bounds.w;
			settings.bounds.y = (space.height * 0.95 - (settings.bounds.h * 2))-16;
		}
		var bounds = settings.bounds;
		
		ctx.save();
		ctx.fillStyle = "white";
		ctx.translate(bounds.x, bounds.y);
		var fontSize = rat.math.floor(12 * 1/rat.graphics.globalScale.x);
		ctx.font = "bold " + fontSize + "px monospace";
		
		//	the marks
		var x, y;
		for( var markIndex = 0; markIndex < 3; ++markIndex )
		{
			ctx.lineWidth = 1;
			ctx.strokeStyle = (["red", "yellow", "blue"])[markIndex];
			ctx.beginPath();
			y = (markIndex/2) * bounds.h;
			ctx.moveTo(0, y);
			ctx.lineTo(bounds.w, y);
			ctx.stroke();
		}
		
		//	We need two records to draw.
		if( !window.performance || !window.performance.memory )
		{
			ctx.textAlign = "start";
			ctx.textBaseline = "hanging";
			ctx.fillText("Memory unavailable.", 0, bounds.h+1);
		}
		else
		{
			var ttl = window.performance.memory.totalJSHeapSize || 1;
			var used= window.performance.memory.usedJSHeapSize || 0;
			var percent = (used/ttl)*100;
			settings.history.push( used );
			if( settings.history.length > rat.system.FPS_RECORD_SIZE )
				settings.history.shift();
			ctx.textAlign = "start";
			ctx.textBaseline = "hanging";
			ctx.fillText("Memory: " + percent.toFixed(2) + "% of " + (ttl / 1024 / 1024).toFixed(2) + "MB", 0, bounds.h+1);
			
			ctx.strokeStyle = "green";
			ctx.save();
			ctx.beginPath();
			for( var index = 0; index < settings.history.length && index < rat.system.FPS_RECORD_SIZE; ++index )
			{
				var visualIndex = index;
				if( settings.history.length < rat.system.FPS_RECORD_SIZE )
					visualIndex += (rat.system.FPS_RECORD_SIZE -settings.history.length);
				var record = settings.history[index];
				var x = bounds.w * (visualIndex/(rat.system.FPS_RECORD_SIZE-1));
				var y = bounds.h * (1.0-(record/ttl));
				if( index == 0 )
					ctx.moveTo( x, y );
				else
					ctx.lineTo( x, y );
			}
			ctx.stroke();
			ctx.restore();
		}
		ctx.restore();
	};
		
	//	show debug frame rate graph
	rat.system.drawDebugFramerateGraph = function ()
	{
		//	We need two records to draw.
		var recordCount = gDeltaTimeRawRecord.length;
		if (recordCount <= 1)
			return;

		var ctx = rat.graphics.getContext();
		//var canvas = rat.graphics.canvas;
		var space = {width : rat.graphics.SCREEN_WIDTH, height : rat.graphics.SCREEN_HEIGHT};
		var graph = rat.system.debugFramerateGraphSettings;
		//	STT changed this 2015.3.3
		//	It had the problem that it was only getting set the first time, so if screen resized, it wasn't updating, so it ended up in lame placs.
		//	But I want to respect the idea of having defined settings or not.
		//	So, now we check if there were any settings and recalculate local values ourselves each frame instead of setting the globals.
		//	Another approach would be to copy whatever is set in debugFramerateGraphSettings and just fill in what's missing each frame.
		//	Also note that if some project is actually using debugFramerateGraphSettings, they'd better be adapting to resizes!  :)
		if (!graph || !graph.bounds || !graph.bounds.x)
		{
			graph = {bounds:{}};	//	make a new local temp settings object
			graph.bounds.w = space.width/4;
			graph.bounds.h = space.height/8;
			graph.bounds.x = space.width * 0.90 - graph.bounds.w;
			graph.bounds.y = space.height * 0.95 - graph.bounds.h;
		}
		
		ctx.translate(graph.bounds.x, graph.bounds.y);

		//	the marks
		ctx.lineWidth = 1;
		ctx.strokeStyle = "#F02040";
		ctx.beginPath();
		ctx.moveTo(0, 0);
		ctx.lineTo(graph.bounds.w, 0);
		ctx.stroke();

		ctx.beginPath();
		ctx.moveTo(0, graph.bounds.h / 2);
		ctx.lineTo(graph.bounds.w, graph.bounds.h / 2);
		ctx.stroke();

		ctx.beginPath();
		ctx.moveTo(0, graph.bounds.h);
		ctx.lineTo(graph.bounds.w, graph.bounds.h);
		ctx.stroke();

		//	What is the top mark
		var topMark = 120; // 120 fps

		function drawChart(vals, startIndex)
		{
			ctx.beginPath();
			var point = {
				x: 0,
				y: 0
			};
			var len = vals.length;
			var xShift = graph.bounds.w / (FPS_RECORD_SIZE-1);
			var p;
			for (var index = 0; index !== len; ++index)
			{
				p = vals[(index + startIndex) % len];
				p = (1 / p) / topMark;
				point.y = graph.bounds.h - (p * graph.bounds.h);
				
				if (index === 0)
					ctx.moveTo(point.x, point.y);
				else
					ctx.lineTo(point.x, point.y);

				point.x += xShift;
			}
			ctx.stroke();
		}

		ctx.lineWidth = 2;

		//	Draw profile markers if we are using rat.profiler and have some
		var p;
		if (rat.profiler.perfmarkers.length > 0)
		{
			ctx.save();

			var list = rat.profiler.perfmarkers;

			//	What is the current frame
			var curFrame = rat.graphics.frameIndex;

			var marker;
			var tx;
			for (var index = 0; index !== list.length; ++index)
			{
				marker = list[index];
				p = 1-((curFrame - marker.frame) / FPS_RECORD_SIZE);

				ctx.strokeStyle = marker.color;
				ctx.beginPath();
				tx = graph.bounds.w * p;
				ctx.moveTo(tx, 0);
				ctx.lineTo(tx, graph.bounds.h);
				ctx.stroke();
			}

			ctx.restore();
		}
		
		//	Draw the FPS records
		//	Second - raw
		ctx.strokeStyle = "#808000";
		drawChart(gDeltaTimeRawRecord, gFPSRecordIndex);

		//	Draw the FPS records
		//	First - smoothed
		ctx.strokeStyle = "#8080FF";
		drawChart(gDeltaTimeRawSmoothedRecord, gFPSRecordIndex);

		//	FPS text
		//	Scale this up to be legible by default in high res.
		var fontSize = rat.math.floor(12 * 1/rat.graphics.globalScale.x);
		var dispFps = ((rat.runningFps*100)|0)/100;
		var dispDT = ((rat.deltaTime * 1000) | 0) / 1000;
		var lowFPS = 1/gHighDeltaTime;
		ctx.fillStyle = "#F0F0F0";
		ctx.font = "bold " + fontSize + "px monospace";
		ctx.textBaseline = "hanging";
		ctx.TextAlign = "left";
		dispFps = "" + dispFps.toFixed(2);
		dispDT = "" + dispDT.toFixed(3);
		lowFPS = "" + lowFPS.toFixed(2);
		var padder = "     ";
		dispFps = padder.substr(0, padder.length - dispFps.length) + dispFps;
		dispDT = padder.substr(0, padder.length - dispDT.length) + dispDT;

		ctx.fillText("fps: " + dispFps + " (low "+ lowFPS+")  DT: " + dispDT, 0, graph.bounds.h);

		ctx.translate(-graph.bounds.x, -graph.bounds.y);
	};

	//
	//	send a rat/game command down the screen stack.
	//	This lets any screen step in and handle the command.
	//	todo:  merge with the above functions
	//
	rat.dispatchCommand = function (command, info)
	{
		//	first see if any screen in the stack is going to handle this.
		//	this is for popups, for example.
		if( rat.screenManager )
		{
			for(var i = rat.screenManager.screenStack.length - 1; i >= 0; i--)
			{
				var screen = rat.screenManager.screenStack[i];
				if(typeof screen.handleCommand !== 'undefined')
				{
					var handled = screen.handleCommand(command, info);
					//	note that the screen might have popped itself, but the way we're handling this in reverse order is OK.
					if(handled)
						return;
				}
			}
		}
		
		//	then hand to whatever registered command handler there is.
		if(rat.system.commandHandler)
			rat.system.commandHandler(command, info);
	};
	
	rat.system.exit = function()
	{
		//	note: LE case overridden in LE module
		if (rat.system.has.Wraith) {
			if (window.close)
				window.close();
		} else {
			//	can't close in a browser.
			rat.console.log( ">>>>APP REQUESTED CLOSE, but we are just a poor browser and don't know how.<<<<" );
		}

		// all other systems do nothing for now
	};
	
	rat.system.reloadLocation = function(forceGet)
	{
		//	note: LE case overridden in LE module
		if (forceGet === void 0)
			forceGet = true;
		if (rat.system.has.windows8 || rat.system.has.xbox)
		{
			//	todo: some specific implementation here?
		} else {
			document.location.reload(forceGet);
		}
	};
	rat.system.assignLocation = function(newURL)
	{
		//	note: LE case overridden in LE module
		
		//	todo: different target system compatibility
		//	todo: what is "url" here?  a relative path from where we started?  A fully qualified path including protocol?
		//	consider these variables for usefulness:
		//	var url = location.protocol + '//' + location.host + location.pathname;
		
		{
			document.location.assign(newURL);
		}
	};
	

	/**
	* util to use the proper event handling for IE and other browsers
	* @param {boolean=} capture optional
	*/
	rat.addOSEventListener = function (element, event, func, capture)
	{
		if (element && element.addEventListener)
			return element.addEventListener(event, func, !!capture);
		else if (element && element.attachEvent)
			return element.attachEvent(event, func);
		else if (typeof (window) !== 'undefined' && window.addEventListener)
			return window.addEventListener(event, func, !!capture);
	};

	/**
	* util to remove the proper event handling for IE and other browsers
	* @param {boolean=} capture optional
	*
	* If an event listener retains any data (such as inside a closure), use this
	* function to remove that data reference. Called with the same parameters as
	* rat.addOSEventListener that added it.
	*/
	rat.removeOSEventListener = function (element, event, func, capture)
	{
		if (element.removeEventListener)
			element.removeEventListener(event, func, !!capture);
		else if (element.detachEvent)
			element.detachEvent(event, func);
		else if (typeof window !== 'undefined' && window.removeEventListener)
			window.removeEventListener(event, func, !!capture);
	};

	/**
	* add key event listener, and standardize some values in event
	* @param {boolean=} capture optional
	*/
	rat.addOSKeyEventListener = function (element, event, func, capture)
	{
		rat.addOSEventListener(element, event, function (e)
		{
			if( rat.input )
				rat.input.standardizeKeyEvent(e);
			func(e);
		}, capture);
	};

	/**                                                                              
	    Handle event dispatches for error, suspend and resume as fired by WinJS apps
 */

	//	Fire on any error
	// A global error handler that will catch any errors that are not caught and handled by your application's code.
	// Ideally the user should never hit this function because you have gracefully handled the error before it has 
	// had a chance to bubble up to this point. In case the error gets this far, the function below will display an
	// error dialog informing the user that an error has occurred.
	function onError(event)
	{
		rat.console.log("JS ERROR! " + JSON.stringify(event));
		if (rat.events && rat.events.fire)
			rat.events.fire("error", { sysEvent: event });

		var errorDialog = new window.Windows.UI.Popups.MessageDialog(
			event.detail.errorUrl +
				"\n\tLine:\t" + event.detail.errorLine +
				"\tCharacter:\t" + event.detail.errorCharacter +
				"\nMessage: " + (event.detail.message || event.detail.errorMessage),
			(event.type === "error") ? "Unhanded Error!" : event.type);
		errorDialog.cancelCommandIndex = 0;
		errorDialog.defaultCommandIndex = 0;
		errorDialog.showAsync();
		return true;
	}

	//	Fired when we are resumed from a suspend.
	function onResume(event)
	{
		var args = { sysEvent: event };
		if (rat.events && rat.events.fire)
			rat.events.fire("resume", args);
		//	How did we get a resume before we finished loading in rat...
	}

	//	Fired when the app is going to be suspended
	function onSuspend(event)
	{
		var sysEvent = event;	//	Cannot leave this as a param or it isn't available to our sub-functions

		//	If we have async stuff to do...
		var busyCount = 0;
		var promise;
		function setBusy()
		{
			++busyCount;
			if (busyCount === 1 && rat.system.has.winJS)
			{
				sysEvent.setPromise(
					new window.WinJS.Promise(function (completeDispatch, errorDispatch, progressDispatch)
					{
						promise = completeDispatch;
					})
				);
			}
		}

		//	Set that we are done with our work.
		function setDone()
		{
			if (busyCount <= 0)
				return;

			--busyCount;
			if (busyCount === 0 && promise)
				promise("Done");
		}

		//	Set what to save with the suspend.
		var sessionData = {};
		function storeData(data)
		{
			sessionData = data;
		}

		//	Start busy.
		setBusy();

		//	Firing the event out into the world.
		var args = { store: storeData, setBusy: setBusy, setDone: setDone, sysEvent: sysEvent };
		if (rat.events && rat.events.fire)
			rat.events.fire("suspend", args);

		//	Save any data set
		if (rat.system.has.winJS)
			window.WinJS.Application.sessionState["savedState"] = sessionData;

		//	And we are done working in this file... Note that we may still be pending elsewhere.
		setDone();
	}

	var hasSuspendResume = false;
	if (rat.detectPlatform.detected)
		hasSuspendResume = rat.system.has.winJS;
	else
		hasSuspendResume = !!window.Windows;

	/** TODO Wraith will need to fire similar (or the same) events if we event ship a JS app running in wraith under Windows 8 or as an XboxOne app
 */
	if (hasSuspendResume && window.WinJS)
	{
		/**  [JSH 1/20/2015] most of this is copied and modified from what was found in agent.
 */

		window.WinJS.Application.onerror = onError;
		window.WinJS.Application.oncheckpoint = onSuspend;
		window.Windows.UI.WebUI.WebUIApplication.addEventListener("resuming", onResume, false);
	}
	
	//	Support visibility detection.
	//	See http://stackoverflow.com/questions/1060008/is-there-a-way-to-detect-if-a-browser-window-is-not-currently-active
	(function() {
		var hidden = "hidden";
		
		function onchange (evt) {
			var v = "visible", h = "hidden",
			evtMap = {
			focus:v, focusin:v, pageshow:v, blur:h, focusout:h, pagehide:h
			};

			evt = evt || window.event;
			var result;
			if (evt.type in evtMap)
				result = evtMap[evt.type];
			else
				result = this[hidden] ? "hidden" : "visible";
			
			if (result && rat.events && rat.events.fire)
				rat.events.fire("visibility", result);
		}

		// Standards:
		if (hidden in document)
			document.addEventListener("visibilitychange", onchange);
		else if ((hidden = "mozHidden") in document)
			document.addEventListener("mozvisibilitychange", onchange);
		else if ((hidden = "webkitHidden") in document)
			document.addEventListener("webkitvisibilitychange", onchange);
		else if ((hidden = "msHidden") in document)
			document.addEventListener("msvisibilitychange", onchange);
		// IE 9 and lower:
		else if ("onfocusin" in document)
			document.onfocusin = document.onfocusout = onchange;
		// All others:
		else
			window.onpageshow = window.onpagehide = window.onfocus = window.onblur = onchange;

		//	set the initial state (but only if browser supports the Page Visibility API)
		//	this doesn't currently work because rat.events doesn't exist yet.
		//	I'm not anxious to make this module dependent on that one.
		//if( document[hidden] !== undefined )
		//	onchange({type: document[hidden] ? "blur" : "focus"});
	})();

	/**                                                                              
	   	Handle focus/blur events so we can now if the app is in focus
 */
	rat.addOSEventListener( window, "focus", function ()
	{
		if (!rat.system.hasFocus)
		{
			//rat.console.log("App has focus");
			rat.system.hasFocus = true;
			if (rat.events && rat.events.fire)
				rat.events.fire("focus");
		}
	});

	rat.addOSEventListener(window, "blur", function ()
	{
		if (rat.system.hasFocus)
		{
			//rat.console.log("App does NOT have focus");
			rat.system.hasFocus = false;
			if (rat.events && rat.events.fire)
				rat.events.fire("blur");
		}
	});
	
});
//--------------------------------------------------------------------------------------------------
//
//	A general os event registration and dispatch system
//	This lets us register and remove registration for events without worrying about
//	what callbacks were already registered.
//	We also do some queueing of some events and report them as soon as a listener is registered.
//	
//
rat.modules.add( "rat.os.r_events",
[ ], 
function(rat)
{
	rat.events = rat.events || {};
	rat.events.registered = {};	//	key is event name.   value is an array 
	rat.events.firing = {};		//	if we are firing an event, hold the data that will allow safe removal of events.
	rat.events.queued = rat.events.queued || {};
	
	/**  Register a new event listener
 */
	rat.addEventListener = function( event, func, ctx )
	{
		if( !func )
			return;
		rat.events.registered[event] = rat.events.registered[event] || [];
		var listeners = rat.events.registered[event];
		listeners.push({
			func: func,
			ctx: ctx
		});
		
		//	Some special handling for events that where queued until we got a listener
		if( rat.events.queued[event] )
		{
			rat.events.fire.apply( rat.events, rat.events.queued[event] ); // queued is an array.  Inlcudes the name of the event.
			rat.events.queued[event] = void 0;
		}
	};
	
	/**  UnRegister an event listener
 */
	rat.removeEventListener = function( event, func, ctx )
	{
		if( !func )
			return false;
		var listeners = rat.events.registered[event];
		if( !listeners )
			return false;
		
		//	Search
		var firing = rat.events.firing[event];
		var listener;
		for( var index = 0; index !== listeners.length; ++index )
		{
			listener = listeners[index];
			if( listener.func === func && listener.ctx === ctx )
			{
				//	Make sure that any events that we are already firing are ok
				if( firing )
				{
					if( firing.index <= index )
						--firing.index;
					--firing.stopAt;
				}
				
				//	Remove the event.
				listeners.splice( index, 1 );
				return true;
			}
		}
		
		//	Not found
		return false;
	};
	
	//	Queue an event until we have added a listener
	rat.events.queueEvent = function (event /*,[arg, arg, ...]*/)
	{
		var fireArgs = Array.prototype.slice.call(arguments);
		var listeners = rat.events.registered[event];
		if (listeners && listeners.length)
			rat.events.fire.apply(rat.events, fireArgs);
		else
			rat.events.queued[event] = fireArgs;
	};
	
	//	Fire an event
	rat.events.fire = function (event /*,[arg, arg, ...]*/)
	{
		//rat.console.log("rat firing event " + event);
		
		var listeners = rat.events.registered[event];
		if( !listeners || listeners.length === 0 )
			return false;
		var args = Array.prototype.slice.call(arguments, 1);
		var savedFiring = rat.events.firing[event];
		
		//	We use this object so we can safely remove objects while iterating over an array
		rat.events.firing[event] = {
			index: 0,
			stopAt: listeners.length
		};
		var firing = rat.events.firing[event];
		var listener, func, ctx;
		for( ; firing.index !== firing.stopAt; ++firing.index )
		{
			listener = listeners[firing.index];
			func = listener.func;
			ctx = listener.ctx;
			func.apply( ctx, args );
			
			/** TODO Add some system where we can stop any more events from firing..
 */
		}
		
		rat.events.firing[event] = savedFiring;
	};
	
});
//--------------------------------------------------------------------------------------------------
//
//	rat console
//
//	This is a custom interactive console system, independent of any host console system.
//	This is really nice for a few reasons...
//		* we can customize it like crazy and add simple custom commands
//			(including game-specific commands) that do whatever we want.
//		* it works at runtime in-game on systems without console debugging or even debug output
//			(like game-console browsers, among others).
//		* it's pretty easy to use
//		* it has nice features like remembering history across sessions
//		* it has extra functionality for filtering and processing debug output
//		* more...
//
//	This console tries hard not to depend on other parts of rat,
//		because it needs to function even if rat hasn't been entirely set up,
//		e.g. in order to help debug rat setup issues...
//
//	todo: move some of these debug utils to r_debug module maybe?
 
//	console namespace
rat.modules.add( "rat.debug.r_console",
[
	"rat.os.r_system", 
	"rat.math.r_math", 
	"rat.graphics.r_graphics", 
	"rat.utils.r_utils", 
	"rat.input.r_input",
], 
function(rat)
{
	rat.console.config = {
		bounds : {x:48, y:0, w:{fromParent:true, val:-(48*2)}, h:{percent:true, val:0.5}},
		textColor : "#90B090",
		textSize : 12,
		logLineHeight : 14,	//	should be similar to text size
		bgColor : "rgba(0,0,0,0.5)",
	};
	rat.console.configChanged = {};	//	see writeConfig below - a hash of explicitly changed properties for saving/loading
	
	rat.console.state = {
		consoleAllowed:	false,
		consoleActive: false,
		cursorPos: 0,
		pulseTimer: 0,
		currentCommand: "",

		//	Registered commands
		nextCommandID: 0,			//	The next ID to provide to a registered command
		commandList: [],			//	The list of available commands
		commandHistory: [],			//	History of entered commands
		commandHistoryIndex: -1,	//	What history item am i accessing (up/down)  -1 is im not
		commandHistoryMax: 100,		//	Remember up to 100 commands

		//	For auto-complete
		autoComplete: [],			//	List of currently built auto-completion options
		accessingAutoComplete: -1,	//	where are we in the auto-completion list

		logOffset: 0,				// How to shift the log display
		
		consoleKey: '/',				// What key to use to open the console
		//	what modifiers to hold for consoleKey above to kick in.
		//	this helps in environments where / is being used by something else!
		//	by default, no modifiers!
		consoleAltKey : false,		
		consoleCtrlKey : false,		
		consoleShiftKey : false,		
	};
	var state = rat.console.state;

	var storageCmdHistoryKey = "rat_commandHistory";
	var storageConsoleConfigKey = "rat_consoleConfig";
	
	//	Load the command history from local storage if it is there
	//	As with other console features, this is fairly low-level code, directly accessing localStorage
	//	instead of using the rat storage object, since we need the console to be up and running before other modules.
	var storage = rat.system.getLocalStorage();
	if (storage)
	{
		var hist = storage.getItem(storageCmdHistoryKey);
		if (hist)
			rat.console.state.commandHistory = JSON.parse(hist);

		//	Load config from local storage if there
		var scon = storage.getItem(storageConsoleConfigKey);
		if (scon)
		{
			var scon = JSON.parse(scon);
			//	explicitly check for some settings that need to be constructed manually?
			
			//	assume everything else can be assigned directly,
			//	but do it one element at a time, keeping any defaults not explicitly found in the saved config.
			for (var key in scon)
			{
				if (rat.console.config[key])
				{
					rat.console.config[key] = scon[key];
					rat.console.configChanged[key] = true;	//	remember the user wanted this changed
				}
			}
		}
	}
	
	//
	//	Write out (changed) config properties
	//
	//	Write only what has changed!  We track property names at the top level, whenever the user actively changes something from the console.
	//	Why?  Because we don't want to write out permanent user settings for something the game might later decide has a better *default*,
	//	and if the user didn't explicitly change it, let's leave it.
	//	And what I mean by the user changing it is:  They actively used the console UI to change something like font size.
	//	You'll notice in the various "set" functions here like setBounds and setTextSize, we don't flag changes,
	//	because we know those might be calls from game code setting up defaults.
	//	It's only explicit changes here in the console that we want to save and use to override defaults.
	//	As of 2016.9.20, that means only textSize and logLineHeight get changed 'cause that's all we have controls for.
	//	though it's possible in the console to manually set values and call rat.console.writeConfig by hand from the console.
	rat.console.writeConfig = function(propNames)
	{
		//	mark desired properties as changed (support individual prop or list of props)
		if (!Array.isArray(propNames))
			propNames = [propNames];
		for (var i = 0; i < propNames.length; i++)
			rat.console.configChanged[propNames[i]] = true;
		
		//	now try to save out
		if (rat.system.localStorageObject)
		{
			var config = {};
			for (var key in rat.console.configChanged)
			{
				config[key] = rat.console.config[key];	//	OK to be a reference - we'll throw away our local config object below
			}
			rat.system.localStorageObject.setItem(storageConsoleConfigKey, JSON.stringify(config));
		}
	};
	
	//	clear out config entirely
	rat.console.clearConfig = function()
	{
		if (rat.system.localStorageObject)
			rat.system.localStorageObject.removeItem(storageConsoleConfigKey);
		rat.console.configChanged = {};
		//	expect user to refresh or something at this point to reload defaults
	};
	
	rat.console.setBounds = function(x, y, w, h)
	{
		rat.console.config.bounds.x = x;
		rat.console.config.bounds.y = y;	//	ignored, currently
		rat.console.config.bounds.w = w;	//	ignored, currently
		rat.console.config.bounds.h = h;	//	ignored, currently
	};
	
	rat.console.setTextSize = function(size, lineHeight)
	{
		rat.console.config.textSize = size;
		
		if (!lineHeight)
			lineHeight = Math.floor(size * 1.1);	//	autocalculate to be 20% bigger than text height
		
		rat.console.config.logLineHeight = lineHeight;
	};

	//	Utility for resetting auto-complete
	function resetAutoComplete()
	{
		state.autoComplete = [];
		state.accessingAutoComplete = -1;
	}

	//	Utility function to handle setting the current command
	function setCurCommand(cmd, resetHistory, resetAutocomplete)
	{
		state.currentCommand = cmd;
		state.cursorPos = rat.math.min( rat.math.max(0, state.cursorPos), state.currentCommand.length);
		if (resetHistory)
			state.commandHistoryIndex = -1;
		if (resetAutocomplete)
			resetAutoComplete();
	}

	function getGlobalVar(name)
	{
		//	for now, use rat.utils.get.
		//	This has some weaknesses.  Maybe change this one day?
		//	I'm a little concerned about security if we start using eval().
		return rat.utils.get(this, name);
	}

	//	Build the auto-complete list
	function buildAutocomplete(cmd)
	{
		//	Start with an empty list.
		state.autoComplete = [];

		//	The commands must START with cmd to fit.
		var upperCmd = cmd.toUpperCase();
		var commandCount = state.commandList.length;
		for (var commandIndex = 0; commandIndex !== commandCount; ++commandIndex)
		{
			//	Is this the matching command
			//	If the command matches, do NOT put its aliases in the list
			var curCmd = state.commandList[commandIndex];
			if (curCmd.name.slice(0, upperCmd.length) === upperCmd )
			{
				state.autoComplete.push(curCmd.name);
				continue;
			}

			//	Check its aliases
			var aliasCount = curCmd.alias.length;
			for (var aliasIndex = 0; aliasIndex !== aliasCount; ++aliasIndex)
			{
				if (curCmd.alias[aliasIndex].slice(0, upperCmd.length) === upperCmd )
					state.autoComplete.push(curCmd.alias[aliasIndex]);
			}
		}

		//	IF we found nothing, maybe we are entering javascript..   Variable name completion
		if (state.autoComplete.length <= 0)
		{
			//	Find the leading variable name...
			//	Do this by finding the last '.'
			var fullVarNameEndsAt = cmd.lastIndexOf('.');
			if (fullVarNameEndsAt > 1)//	We MUST find something (app or rat or some other global)
			{
				var fullVarName = cmd.substring(0, fullVarNameEndsAt);
				var mustStartWith = cmd.substring(fullVarNameEndsAt + 1);
				//if( mustStartWith )
				//	rat.console.log("Auto complete for variables in '" + fullVarName + "' starting with '" + mustStartWith + "'");
				//else
				//	rat.console.log("Auto complete for variables in '" + fullVarName + "'");

				//	get the variable
				var variable = getGlobalVar(fullVarName);
				if (variable !== void 0)
				{
					for (var key in variable)
					{
						if( key )
						{
							if( mustStartWith )
							{
								var startsWith = key.substring(0, mustStartWith.length);
								if (startsWith !== mustStartWith)
									continue;
							}

							state.autoComplete.push(fullVarName + "." + key);
						}
					}
				}
			}
				

		}

		//	Alphabetical order
		state.autoComplete.sort();
		state.accessingAutoComplete = 0;
	}

	//	Allow the console
	rat.console.allow = function (allow)
	{
		if (allow === void 0)
			allow = true;
		rat.console.state.consoleAllowed = !!allow;
	};
	
	//	Activate the console (make it visible!)
	//	Normally this is not needed, because the console is activated with user input.
	//	But on some systems, e.g. phone or console, we sometimes want the console to be showing
	//	right away.  So, this function will activate directly.
	rat.console.activate = function (doActivate)
	{
		if (doActivate === void 0)
			doActivate = true;
		rat.console.state.consoleActive = !!doActivate;
	};
	
	//	Log this (type of) output once, recording that we've logged it,
	//	so we can suppress future messages of that type.
	rat.console.logOnce = function (text, name, count)
	{
		if (!name)	//	if name not specified, use full text message to identify it
			name = text;
		
		if (!count)	//	optional count (to allow this message more than once, but still limited)
			count = 1;
		
		if (!rat.console.onceRecord[name])
			rat.console.onceRecord[name] = 0;
		rat.console.onceRecord[name]++;
		if (rat.console.onceRecord[name] <= count)
		{
			rat.console.log(text);
		}
	};

	//	Draw the console
	rat.console.drawConsole = function ()
	{
		//	Bail if we are either not allowed or not active.
		if (!rat.console.state.consoleAllowed ||
			!rat.console.state.consoleActive)
			return;

		//	Draw at the bounds set.
		//	This includes drawing the log and any input that we have
		//	Find out what the bounds mean
		var bounds = rat.console.config.bounds;
		if (rat.ui && rat.ui.data && rat.ui.data.calcBounds)
		{
			var ratBounds = {
				x: 0,
				y: 0,
				w: rat.graphics.SCREEN_WIDTH,
				h: rat.graphics.SCREEN_HEIGHT
			};
			bounds = rat.ui.data.calcBounds({ bounds: bounds }, ratBounds);
		}

		//	Draw the BG
		rat.graphics.drawRect(
			bounds,
			{ fill: true, color: rat.console.config.bgColor });
		rat.graphics.drawRect(
			bounds,
			{ color: rat.console.config.bgColor, lineWidth:2 });

		//	Draw the log
		var logBounds = rat.utils.copyObject( bounds );
		logBounds.h -= (rat.console.config.logLineHeight + 10);
		rat.console.drawLog(logBounds);

		//	Draw the line between the log and the input
		logBounds.y = logBounds.h;
		logBounds.h = bounds.h - logBounds.h;
		rat.graphics.drawRect(logBounds, { color: rat.console.config.bgColor, lineWidth: 2 });

		//	Draw the running time
		logBounds.x += 4;
		logBounds.y += 4;

		var time = rat.runningTime || 0;
		var hours = (time/60/60)|0;
		time -= hours * 60 * 60;
		var minutes = (time/60)|0;
		time -= minutes * 60;
		var seconds = time|0;
		var timeText = "" + hours + ":" + minutes + ":" + seconds;
		var width = rat.graphics.ctx.measureText(timeText);
		if (width.width !== void 0)
			width = width.width;
		rat.graphics.drawText(timeText, logBounds.x + logBounds.w - width - 8, logBounds.y);

		//	Draw the current console text
		
		rat.graphics.drawText("> " + state.currentCommand, logBounds.x, logBounds.y);

		//	Draw the cursor
		state.pulseTimer += rat.deltaTime;
		if (state.pulseTimer > 0.6)
			state.pulseTimer = 0;
		if (state.pulseTimer < 0.3)
		{
			var subCmd = state.currentCommand.slice(0, state.cursorPos);
			width = rat.graphics.ctx.measureText("> " + subCmd);
			width = width.width || width;
			logBounds.x += width;
			rat.graphics.ctx.fillStyle = "white";
			rat.graphics.ctx.font = " bold" + rat.graphics.ctx.font;
			rat.graphics.drawText("|", logBounds.x, logBounds.y );
		}
	};

	//	display log lines of text
	/** @param {Object=} bounds */
	rat.console.drawLog = function (bounds)
	{
		bounds = bounds || rat.console.config.bounds;
		var out = rat.console.output;
		var ctx = rat.graphics.ctx;
		
		var logLineHeight = rat.console.config.logLineHeight;
		
		//	make a copy of my bounds so I can mess with it, if it's not set up.
		var myBounds = {x:bounds.x, y:bounds.y, w:bounds.w, h:bounds.h};
		if (myBounds.w < 0)
			myBounds.w = rat.graphics.SCREEN_WIDTH - myBounds.x - 10;
		if (myBounds.h < 0)
			myBounds.h = rat.graphics.SCREEN_HEIGHT - myBounds.y - 10;

		ctx.font = '' + rat.console.config.textSize + 'px Arial';
		ctx.textAlign = 'left';
		ctx.fillStyle = rat.console.config.textColor;//"#B0FFA0";

		//	start high enough so that our last line is at the bottom.
		
		var yPos = myBounds.h - 0 - logLineHeight * out.length;
		var bottom = myBounds.h + myBounds.y;
		yPos += (rat.console.state.logOffset-1) * logLineHeight;
		ctx.textBaseline = "top";//"bottom";
		for (var i = 0; i < out.length; i++)
		{
			if( (yPos + logLineHeight) > myBounds.y )
				ctx.fillText(out[i], myBounds.x, yPos, myBounds.w);
			yPos += logLineHeight;
			if( (yPos + logLineHeight) >= bottom && rat.console.state.logOffset > 0 )
			{
				ctx.fillText("...", myBounds.x, yPos, myBounds.w);
				yPos += logLineHeight;
			}
			if( yPos >= bottom )
				break;
		}
		
		//ctx.strokeStyle = "#FFFFFF";
		//ctx.strokeRect(myBounds.x, myBounds.y, myBounds.w, myBounds.h);
	};

	//	Get events
	rat.console.handleEvent = function (ratEvent)
	{
		if (ratEvent.eventType !== "keydown" && ratEvent.eventType !== "keypress")
			return false;

		if (ratEvent.eventType === "keydown")
		{
			//	Don't eat F12 - we want to be able to toggle browser debug window, still.
			//	(but what about other events like F5 and F8 - are those already getting allowed somehow?)
			if (ratEvent.which === rat.keys.f12)
				return false;

			//	console key (if it's unmodified!) will toggle console activity.
			//	Escape key will only make console inactive.
			if ((
					ratEvent.which === rat.keys[state.consoleKey]
					&& (ratEvent.sysEvent.altKey === state.consoleAltKey
						&& ratEvent.sysEvent.ctrlKey === state.consoleCtrlKey
						&& ratEvent.sysEvent.shiftKey === state.consoleShiftKey))
				||
					(ratEvent.which === rat.keys.esc && rat.console.state.consoleActive)
				)
			{
				rat.console.state.consoleActive = !rat.console.state.consoleActive;
				return true;
			}

			//	Not active means not handled.
			if (!rat.console.state.consoleActive)
				return false;
			else
				ratEvent.allowBrowserDefault = true;

			//	Run the cmd.
			if (ratEvent.which === rat.keys.enter)
			{
				ratEvent.sysEvent.preventDefault();
				state.currentCommand = state.currentCommand.trim();
				if (state.currentCommand && state.commandHistory[state.commandHistory.length] !== state.currentCommand)
				{
					state.commandHistory.unshift(state.currentCommand);
					if (state.commandHistory.length > state.commandHistoryMax)
						state.commandHistory.pop();
					if (rat.system.localStorageObject)
						rat.system.localStorageObject.setItem(storageCmdHistoryKey, JSON.stringify(state.commandHistory));
				}
				if( state.currentCommand )
					rat.console.parseCommand(state.currentCommand.trim());
				setCurCommand("", true, true);
			}

			//	Erase
			else if (ratEvent.which === rat.keys.backspace)
			{
				ratEvent.sysEvent.preventDefault();
				if (state.cursorPos > 0)
				{
					--state.cursorPos;
					setCurCommand(state.currentCommand.slice(0, state.cursorPos) + state.currentCommand.slice(state.cursorPos + 1), false, true);
				}
			}
			else if (ratEvent.which === rat.keys.del)
			{
				ratEvent.sysEvent.preventDefault();
				if (state.cursorPos < state.currentCommand.length)
				{
					setCurCommand(state.currentCommand.slice(0, state.cursorPos) + state.currentCommand.slice(state.cursorPos + 1), false, true);
				}
			}
			
			//	font size change (shift + arrow keys)
			else if (ratEvent.sysEvent.shiftKey && (ratEvent.which === rat.keys.upArrow || ratEvent.which === rat.keys.downArrow))
			{
				var size = rat.console.config.textSize;
				if (ratEvent.which === rat.keys.upArrow)
					size += 2;
				else if (ratEvent.which === rat.keys.downArrow)
				{
					size -= 2;
					if (size < 4)	//	let's be reasonable
						size = 4;
				}
				rat.console.setTextSize(size);
				rat.console.writeConfig(['textSize', 'logLineHeight']);	//	remember what we just changed
			}

			//	History access
			else if (ratEvent.which === rat.keys.upArrow || ratEvent.which === rat.keys.downArrow)
			{
				ratEvent.sysEvent.preventDefault();
				setCurCommand(state.currentCommand.trim(), false, false);
				if (ratEvent.which === rat.keys.upArrow)
					++state.commandHistoryIndex;
				else
					--state.commandHistoryIndex;

				//	 If we are less then our current, stay at our current
				if (state.commandHistoryIndex < -1)
					state.commandHistoryIndex = -1;

					//	Did we try to go up with no history
				else if (state.commandHistoryIndex === 0 && state.commandHistory.length === 0)
					setCurCommand("", true, true);

					//	Did we go off the top.
				else if (state.commandHistoryIndex >= state.commandHistory.length)
				{
					state.commandHistoryIndex = state.commandHistory.length - 1;
					setCurCommand(state.commandHistory[state.commandHistoryIndex], false, false);
				}

					//	leaving the history
				else if (state.commandHistoryIndex === -1)
				{
					setCurCommand("", false, false);
				}

					//	We are accessing the history
				else
				{
					setCurCommand(state.commandHistory[state.commandHistoryIndex], false, true);
					state.cursorPos = state.currentCommand.length;
				}
			}
			
			//	Cursor position change
			else if (ratEvent.which === rat.keys.leftArrow || ratEvent.which === rat.keys.rightArrow)
			{
				ratEvent.sysEvent.preventDefault();
				if (ratEvent.which === rat.keys.leftArrow)
					--state.cursorPos;
				else
					++state.cursorPos;
				state.cursorPos = rat.math.max(0, state.cursorPos);
				state.cursorPos = rat.math.min(state.cursorPos, state.currentCommand.length);
			}
			else if (ratEvent.which === rat.keys.home)
			{
				ratEvent.sysEvent.preventDefault();
				state.cursorPos = 0;
			}
			else if (ratEvent.which === rat.keys.end)
			{
				ratEvent.sysEvent.preventDefault();
				state.cursorPos = state.currentCommand.length;
			}

			//	TAB for auto-completion
			else if (ratEvent.which === rat.keys.tab)
			{
				ratEvent.sysEvent.preventDefault();

				//	Find the commands that will complete the current.
				if (state.accessingAutoComplete === -1)
				{
					setCurCommand(state.currentCommand.trim(), false, false);
					buildAutocomplete(state.currentCommand);
				}

				//	Auto-complete only does something if there is something to change to.
				if (state.autoComplete.length > 0)
				{
					setCurCommand(state.autoComplete[state.accessingAutoComplete], true, false);
					state.cursorPos = state.currentCommand.length;
					if (rat.input.keyboard.isKeyDown(rat.keys.shift)) {
						state.accessingAutoComplete--;
						if (state.accessingAutoComplete < 0)
							state.accessingAutoComplete = state.autoComplete.length - 1;
					}
					else {
						state.accessingAutoComplete++;
						if (state.accessingAutoComplete >= state.autoComplete.length)
							state.accessingAutoComplete = 0;
					}
				}
			}
			
			//	PageUp/PageDown to change log offset
			else if (ratEvent.which === rat.keys.pageUp || ratEvent.which === rat.keys.pageDown )
			{
				ratEvent.sysEvent.preventDefault();
				
				if( ratEvent.which === rat.keys.pageUp )
					++rat.console.state.logOffset;
				else
					--rat.console.state.logOffset;
				if( rat.console.state.logOffset >= rat.console.output.length )
					rat.console.state.logOffset = rat.console.output.length-1;
				if( rat.console.state.logOffset < 0 )
					rat.console.state.logOffset = 0;
			}
		}
		else if (ratEvent.eventType === "keypress" )
		{
			//	Not active means not handled.
			if (!rat.console.state.consoleActive)
				return false;
			var char = ratEvent.sysEvent.char;
			if (char.charCodeAt(0) < ' '.charCodeAt(0))
				return false;
			if (state.consoleActive && char !== "\n" && char !== "\t" && char !== state.consoleKey)
			{
				if (state.cursorPos === state.currentCommand.length)
					state.currentCommand += char;
				else if (state.cursorPos === 0)
					state.currentCommand = char + state.currentCommand;
				else
					state.currentCommand = state.currentCommand.slice(0, state.cursorPos) + char + state.currentCommand.slice(state.cursorPos);
				state.cursorPos++;
				resetAutoComplete();
			}
		}
		
		//	otherwise, claim we handled ANY keyboard event, so key events don't go through console and activate game stuff...
		return true; // Handled
	};

	//	Register new commands into the console
	rat.console.registerCommand = function (name, func, aliasList)
	{
		//	Init the new cmd
		var newCmd = {};
		newCmd.name = name.toUpperCase();
		newCmd.func = func;
		newCmd.id = state.nextCommandID++;
		newCmd.alias = aliasList || [];

		//	All commands and alias are uppercase
		var aliasCount = newCmd.alias.length;
		for (var aliasIndex = 0; aliasIndex < aliasCount; aliasIndex++)
			newCmd.alias[aliasIndex] = newCmd.alias[aliasIndex].toUpperCase();

		//	Add it to the cmd list
		state.commandList.push(newCmd);
		return newCmd.id;
	};

	//	Unregister a commands from the console
	rat.console.unregisterCommand = function (id)
	{
		//	Find the command that goes with this
		var commandCount = state.commandList.length;
		for (var commandIndex = 0; commandIndex < commandCount; commandIndex++)
		{
			if (state.commandList[commandIndex].id === id)
			{
				state.commandList.splice(commandIndex, 1);
				return true;
			}
		}

		return false;
	};

	//	Get a command by its name
	rat.console.getCommand = function (cmd)
	{
		var upperCmd = cmd.toUpperCase();
		var count = state.commandList.length;
		for (var index = 0; index !== count; ++index)
		{
			var curCmd = state.commandList[index];
			if (curCmd.name === upperCmd)
				return curCmd;

			for (var aliasIndex = 0; aliasIndex !== curCmd.alias.length; ++aliasIndex)
			{
				if (curCmd.alias[aliasIndex] === upperCmd)
					return curCmd;
			}
		}

		//	Not found
		return void 0;
	};
	
	/**
	  * Run the provided command
	  * @returns true if the command successfully parsed
	  */
	rat.console.parseCommand = function (cmd)
	{
		//	Get the command from the args
		var fullCmd = cmd;
		var cmdArgs = cmd.split(/\s/);
		cmd = cmdArgs[0];
		cmdArgs.splice(0, 1);
		var foundCmd = rat.console.getCommand(cmd);
		if (foundCmd)
		{
			//	Call the command
			foundCmd.func(cmd, cmdArgs);
			return true;
		}
		else
		{
			//	Is the string a full variable?  If so, dump its members or value to the log
			var gblVar = getGlobalVar(fullCmd);
			var dumpVar = fullCmd + " = ";
			if (gblVar !== void 0)
			{
				switch( typeof(gblVar) )
				{
					case "object":
					{
						if (gblVar === null)
							dumpVar += "NULL";
						else
						{
							
							rat.console.log("--------");
							dumpVar += "OBJECT:";
							rat.console.log(dumpVar);
							dumpVar = void 0;
							
							//	Why are we not using something like JSON.stringify here,
							//	or rat.utils.objectToString.  Let's do.
							
							//	by specifying a "name" value here (even though it's just " ")
							//	we get objectToString to bracket it all with {}, which is nice visually.
							var output = rat.utils.objectToString(gblVar, " ");
							rat.console.logNewlines(output);
							
							/*
							var longestName = 0;
							var vals = [];
							var key;
							for (key in gblVar)
							{
								//	We skip some things
								if (key !== "parentClass" && key !== "parentConstructor" && key !== "parent" && key !== "parentPrototype" && key !== "constructor")
								{
									vals.push(key);
									if (key.length > longestName)
										longestName = key.length;
								}
							}

							//	Sort by name?
							vals.sort();
							
							//	print each value, if we can, and its type.
							for (var i = 0; i < vals.length; ++i)
							{
								keyName = vals[i];
								var type = typeof (gblVar[keyName]);
								while (keyName.length < longestName)
									keyName += " ";
								var showVal = "";
								//if (type === 'undefined')
								//	showVal = "";
								//else
								if (type === 'string' || type === 'number' || type === 'boolean')
									showVal = gblVar[vals[i]];
								
								rat.console.log(keyName + "= " + showVal + " : " + type);
							}
							*/
						}
						break;
					}
					case "boolean":
						dumpVar += gblVar.toString();
						break;
					case "number":
						dumpVar += gblVar.toString();
						break;
					case "string":
						dumpVar += gblVar.toString();
						break;
					case "function":
						dumpVar += "function";
						break;
				}
				if( dumpVar )
					rat.console.log(dumpVar);
			}
			else	//	not an interpretable global variable.
			{
				//	We assume this to be raw javascript
				try
				{
					rat.console.log(fullCmd);
					/* jshint -W061 */
					//	STT 2017.3.23 - I'm concerned about supporting this at all.
					//	It seems like a general security problem.  Sure, we try to disable the
					//	console in release versions, but can they find a way to reenable the console?
					var res = eval(fullCmd);
					//	todo: dump result value?  even if undefined?
				}
				catch (e)
				{
					rat.console.log("ERROR: Execution of JS Failed: " + e.description);
				}
			}
		}

		return true;
	};
	
	//	Dump a list of registered commands to the console.
	//	(this is itself a registered command as well)
	//	(Later, if we have many, let's maybe dump a list of categories, and allow an argument for which one to list)
	rat.console.listCommands = function ()
	{
		var count = state.commandList.length;
		for (var index = 0; index !== count; ++index)
		{
			var curCmd = state.commandList[index];
			
			var line = "    " + curCmd.name;
			
			if (curCmd.alias.length)
			{
				line += "  (";
				for (var aliasIndex = 0; aliasIndex !== curCmd.alias.length; ++aliasIndex)
					line += curCmd.alias[aliasIndex] + " ";
				line += ")";
			}
			
			rat.console.log(line);
		}

	};
	
	//	add the "help" command
	rat.console.registerCommand("help", function (cmd, args)
	{
		rat.console.log("-------------------------------------");
		rat.console.log("rat console help:");
		rat.console.log("* up/down arrows navigate command history");
		rat.console.log("* shift up/down arrows change font size");
		rat.console.log("* commands:");
		rat.console.listCommands();
	}, ["?"]);	//	aliases

	//	Add the LOG command
	rat.console.registerCommand("log", function (cmd, args)
	{
		rat.console.log( args.join(" ") );
	});

	//	Toggle the saveLog feature
	rat.console.registerCommand("saveLog", function (cmd, args)
	{
		rat.console.saveOutputToLocalStorage = !rat.console.saveOutputToLocalStorage;
		rat.console.log( "Log will "+ (rat.console.saveOutputToLocalStorage?"":" not ") + " save." );
	});
	
	//	Toggle the saveLog feature
	rat.console.registerCommand("restoreLog", function (cmd, args)
	{
		if( !rat.system.localStorageObject )
			rat.console.log( "ERROR! localStorage not supported" );
		else
		{
			var out = JSON.parse( rat.system.localStorageObject.getItem( "RAT_LOG" ) );
			if( out && Array.isArray(out) )
			{
				rat.console.output = out;
				rat.console.log( "Log restored" );
			}
			else
				rat.console.log( "Failed to restore log" );
		}
	});
	
	//	Add the clear command
	rat.console.registerCommand("clear", function (cmd, args)
	{
		rat.console.output = [];
	}, ["cls", "clearLog"]);

	//	Add the Alias command
	rat.console.registerCommand("Alias", function (cmd, args)
	{
		var aliasCmd = args[0];
		if (aliasCmd)
		{
			args.splice(0, 1);
			rat.console.registerCommand(aliasCmd, function (cmd, args)
			{
				var foundCmd = rat.console.getCommand(cmd);
				rat.console.parseCommand(foundCmd.aliasedTo + " " + args.join(" "));
			});
			var foundCmd = rat.console.getCommand(aliasCmd);
			foundCmd.aliasedTo = args.join(" ");
			rat.console.log("Added alias " + aliasCmd + "=" + foundCmd.aliasedTo);
		}
	});

	//	put frames around ui elements
	rat.console.registerCommand("frames", function (cmd, args)
	{
		rat.dframe();
	}, ["showFrames", "dframe"]);
	
	//	dump ui tree to log
	rat.console.registerCommand("dtree", function (cmd, args)
	{
		rat.dtree();
	}, ["dumpTree"]);

	//	show fps graph
	rat.console.registerCommand("showFPSGraph", function (cmd, args)
	{
		var on = rat.system.debugDrawFramerateGraph;
		rat.system.debugDrawFramerateGraph = !on;
		rat.system.debugDrawTiming = !on;
	}, ["showFPS", "fps"]);
	
	//	show rendering stats, like # ui elements, # particles, etc.
	rat.console.registerCommand("showStats", function (cmd, args)
	{
		var on = rat.system.debugDrawStats;
		rat.system.debugDrawStats = !on;
	}, ["stats"]);
	
	//------
	//	These debug commands are intended to be accepted in the javascript console,
	//	e.g. the developer can type "rat.dframe()" to use one.  So they're intentionally short, but consistent.
	//	These are also now available in rat's console.
	
	//	put debug frame around everything
	rat.dframe = function ()
	{
		if( rat.screenManager )
		{
			var s = rat.screenManager.getTopScreen();
			if (s)
				s.frameAllRandom();
		}
	};
	
	rat.dtree = function ()
	{
		if( rat.screenManager )
		{
			var s = rat.screenManager.getTopScreen();
			if (s)
			{
				var data = s.dumpTree();
				//	actually output that text...
				for (var i = 0; i < data.lines.length; i++)
				{
					rat.console.log(data.lines[i]);
				}
				rat.console.log("total:" + data.totalCount + " (vis: " + (data.totalCount - data.hiddenCount) + ", hid: " + data.hiddenCount + ")");
			} else {
				rat.console.log("no top screen in stack to dump.");
			}
		}
	};
});
//--------------------------------------------------------------------------------------------------
//
//	rat.profiler
//
//	Rat performance profiling calls.
//		Most of these are currently only implemented under Wraith
//

//
// The profiler module
rat.modules.add( "rat.debug.r_profiler",
[
	"rat.debug.r_console",
	"rat.os.r_system",
	"rat.graphics.r_graphics",
	"rat.input.r_input",
], 
function(rat)
{
	rat.profiler = {
		enableProfiler: false,		//	Set to true to enable the profiler code
		usingRatProfiler: false,	//	Are we using the profiler built into rat
		
		//	The following are used when we are using the profiler built into rat
		profileTrees: [
			void 0, 
			void 0
		],
		curNode: void 0,
		enableStatsDisplay: false,
		selectedNode: ["ROOT"],
		displayInfo: {
			atX: 64,			//	Where to draw the profiler information
			atY: 64,
			lineHeight: 22,		//	How high is a single line of profiler information
			tab: "  ",			//	What is a tab
			font: "12px arial",	//	What font
			color: void 0,		//	What color
			highlightColor: void 0,// Color of highlighted node
			shadowColor: "black",// Shadow color
			curY: 0,			//	Runtime value used to draw
			cols: [0, 150, 225, 300, 375, 450], // NAME, % PARENT, % TOTAL, ttl MS, MS/call, # of calls
		},
		highlightedNode: void 0,

		//	For profiler marks in the FPS graph
		perfmarkers: [],
	};

	var perfmarkers = rat.profiler.perfmarkers;

	var topMark = {
		last: void 0,
		name: void 0
	};


	/** Empty perf function
	 * @param {?} name
	 */
	function noAction(name)
	{
	}

	/**                                                                                                
	   	Code for rats embedded profiler
 */

	//	A single profiler node
	function RatProfilerNode(name, parent)
	{
		this._stats = {
			name: name,
			parent: parent,
			time: 0,
			lastTick: void 0,
			calls: 0,
			hasChildren: false
		};
		this._children = {};
	}

	//	Get a child node (or create it if it does not exist)
	RatProfilerNode.prototype.getChild = function( name )
	{
		if (!this._children[name])
		{
			this._children[name] = new RatProfilerNode(name, this);
			this._stats.hasChildren = true;
		}
		return this._children[name];
	};

	//	Clear any held times
	RatProfilerNode.prototype.clear = function()
	{
		this._stats.time = 0;
		this._stats.calls = 0;
		for( var key in this._children )
			this._children[key].clear();
	};

	//	Get the current tick
	RatProfilerNode.prototype.getTick = function()
	{
		if (rat.profiler.usingRatProfiler)
			return window.performance.now();
		else
			return 0;
	};

	//	Store the current tick
	RatProfilerNode.prototype.storeTick = function()
	{
		this._stats.lastTick = this.getTick();
	};

	//	Get the amount of elapsed time
	RatProfilerNode.prototype.updateElapsed = function ()
	{
		this._stats.time += (this.getTick() - this._stats.lastTick) / 1000;
		this._stats.lastTick = void 0;
	};

	//	Starting a new frame
	function ratLevelBeginFrame()
	{
		
	}

	//	Ending a frame
	function ratLevelEndFrame()
	{
		if (rat.profiler.curNode.parent)
		{
			rat.console.log("ERROR!  profiler.push does not match profiler.pop");
			rat.profiler.curNode = rat.profiler.profileTrees[0];
			rat.profiler.curNode._stats.abortProfilerForFrame = true;
		}

		if( !rat.profiler.curNode._stats.abortProfilerForFrame )
		{
			// Should be the full frame time.
			if( rat.profiler.curNode._stats.lastTick !== void 0 )
				rat.profiler.curNode.updateElapsed();
		}

		//	Swap our tree buffers
		var oldTree = rat.profiler.profileTrees[0];
		rat.profiler.profileTrees[0] = rat.profiler.profileTrees[1];
		rat.profiler.profileTrees[1] = oldTree;

		//	Setup for a new pass
		rat.profiler.curNode = rat.profiler.profileTrees[0];
		rat.profiler.curNode.clear();
		rat.profiler.curNode.storeTick();
		++rat.profiler.curNode._stats.calls;
		rat.profiler.curNode._stats.abortProfilerForFrame = false;

		//	Setup the old tree to be ready to display
		oldTree._stats.built = true;
	}

	//	Push a new timing block
	function ratLevelPushPerfMark(name)
	{
		if( rat.profiler.curNode._stats.abortProfilerForFrame )
			return;
		rat.profiler.curNode = rat.profiler.curNode.getChild(name);
		rat.profiler.curNode.storeTick();
		++rat.profiler.curNode._stats.calls;
	}

	//	Pop the last timing block
	function ratLevelPopPerfMark(name)
	{
		if( rat.profiler.curNode._stats.abortProfilerForFrame )
			return;
		if (rat.profiler.curNode._stats.name !== name || rat.profiler.curNode._stats.parent === void 0)
		{
			
			rat.console.log("ERROR!  profiler.push does not match profiler.pop");
			rat.profiler.curNode = rat.profiler.profileTrees[0];
			rat.profiler.curNode._stats.abortProfilerForFrame = true;
			
		}
		else
		{
			rat.profiler.curNode.updateElapsed();
			rat.profiler.curNode = rat.profiler.curNode._stats.parent;
		}
	}

	//	Display some next on the current line.
	function displayProfilerLine(text, atCol, indent)
	{
		indent = indent || 0;
		atCol = atCol || 0;
		var ctx = rat.graphics.ctx;
		while (indent--)
			text = rat.profiler.displayInfo.tab + text;
		if (atCol >= rat.profiler.displayInfo.cols.length)
			atCol = rat.profiler.displayInfo.cols.length - 1;
		var x = rat.profiler.displayInfo.atX + rat.profiler.displayInfo.cols[atCol];
		ctx.fillText(text, x, rat.profiler.displayInfo.curY);
	}

	//	Go down one line
	function gotoNextDisplayLine()
	{
		rat.profiler.displayInfo.curY += rat.profiler.displayInfo.lineHeight;
	}

	//	Get the string version of a percent
	function getPercentDisplay(val, max)
	{
		return (val / max * 100).toFixed(2) + "%";
	}

	//	Get the string version of a time
	function getTimeDisplay(val)
	{
		return val.toFixed(4);
	}

	//	Get the node that is currently being displayed
	function getSelectedNode(node)
	{
		for (var index = 1; index < rat.profiler.selectedNode.length; ++index)
			node = node.getChild(rat.profiler.selectedNode[index]);
		return node;
	}

	//	Dump the profiling information
	function ratLevelDisplayPerfStats()
	{
		if (!rat.profiler.enableStatsDisplay)
			return;
		var ctx = rat.graphics.ctx;
		if (!ctx)
			return;
		ctx.save();

		ctx.font = rat.profiler.displayInfo.font;
		ctx.fillStyle = rat.profiler.displayInfo.color;
		// Draw text shadows is expensive...  Turning this on has serios effects on the FPS, which
		//	When using rat's profiler matters.
		//	So i turned these off.
		// ctx.shadowOffsetX = 1.5;
		// ctx.shadowOffsetY = 1.5;
		// ctx.shadowColor = rat.profiler.displayInfo.shadowColor;

		rat.profiler.displayInfo.curY = rat.profiler.displayInfo.atY;

		var root = rat.profiler.profileTrees[1];
		if (root._stats.abortProfilerForFrame)
			displayProfilerLine("***ERROR WITH PROFILER USAGE***", 0);
		else if (!root._stats.built)
			displayProfilerLine("***STATS PENDING***", 0);
		else
		{
			//	Dump this node
			var node = getSelectedNode(root);
			var parent = node._stats.parent;
			var foundTime = 0;

			//	Header row
			displayProfilerLine("NAME",		0, 2);
			displayProfilerLine("% Parent",	1, 2);
			displayProfilerLine("% Total",	2, 2);
			displayProfilerLine("MS Total",	3, 2);
			displayProfilerLine("MS/call", 4, 2);
			displayProfilerLine("calls", 5, 2);
			gotoNextDisplayLine();
						
			// NAME, % PARENT, % TOTAL, ttl MS, MS/call
			if (parent)
			{
				displayProfilerLine("-" + node._stats.name, 0);	//	Name
				displayProfilerLine(getPercentDisplay(node._stats.time, parent._stats.time), 1);	//	% Parent
			}
			else
				displayProfilerLine(" " + node._stats.name, 0);	//	Name
			displayProfilerLine(getPercentDisplay(node._stats.time,root._stats.time), 2);	//	% Total
			displayProfilerLine(getTimeDisplay(node._stats.time), 3); // TTL MS
			if (node._stats.calls)
			{
				displayProfilerLine(getTimeDisplay(node._stats.time / node._stats.calls), 4); // MS/call
				displayProfilerLine(node._stats.calls, 5); // # of calls
			}
			gotoNextDisplayLine();

			var child;
			for (var key in node._children)
			{
				//	Make sure we have a set highlighted node
				if (!rat.profiler.highlightedNode)
					rat.profiler.highlightedNode = key;
				
				child = node._children[key];
				if (key === rat.profiler.highlightedNode)
					ctx.fillStyle = rat.profiler.displayInfo.highlightColor;
				var preName = " ";
				if( child._stats.hasChildren )
					preName = "+";
				displayProfilerLine(preName + child._stats.name, 0, 1);	//	Name
				displayProfilerLine(getPercentDisplay(child._stats.time, node._stats.time), 1);	//	% Parent
				displayProfilerLine(getPercentDisplay(child._stats.time, root._stats.time), 2);	//	% Total
				displayProfilerLine(getTimeDisplay(child._stats.time), 3); // TTL MS
				if (node._stats.calls)
				{
					displayProfilerLine(getTimeDisplay(child._stats.time / child._stats.calls), 4); // MS/call
					displayProfilerLine(child._stats.calls, 5); // # of calls
				}
				gotoNextDisplayLine();
				if (key === rat.profiler.highlightedNode)
					ctx.fillStyle = rat.profiler.displayInfo.color;
				foundTime += child._stats.time;
			}

			gotoNextDisplayLine();
			displayProfilerLine(" MISSING TIME", 0, 1);	//	Name
			var missingTime = node._stats.time - foundTime;
			displayProfilerLine(getPercentDisplay(missingTime, node._stats.time), 1);	//	% Parent
			displayProfilerLine(getPercentDisplay(missingTime, root._stats.time), 2);	//	% Total
			displayProfilerLine(getTimeDisplay(missingTime), 3); // TTL MS

			//rat.console.log("DUMP STATS FOR NODE " + node._stats.name);
			//displayInfo
		}

		ctx.restore();
	}

	//	Event handling for the profiler
	function ratLevelEventHandler(ratEvent)
	{
		var selected, key;
		if (ratEvent.eventType === "keydown")
		{
			if (rat.profiler.enableStatsDisplay)
			{
				if (ratEvent.which === rat.keys.j)
				{
					if (rat.profiler.selectedNode.length > 1)
						rat.profiler.highlightedNode = rat.profiler.selectedNode.splice(rat.profiler.selectedNode.length - 1, 1)[0];

					return true;
				}
				else if (ratEvent.which === rat.keys.l)
				{
					selected = getSelectedNode( rat.profiler.profileTrees[1] );
					if (selected._children[rat.profiler.highlightedNode] && selected._children[rat.profiler.highlightedNode]._stats.hasChildren)
					{
						rat.profiler.selectedNode.push(rat.profiler.highlightedNode);
						rat.profiler.highlightedNode = void 0;
					}
					return true;
				}
				else if (ratEvent.which === rat.keys.i)
				{
					selected = getSelectedNode(rat.profiler.profileTrees[1]);
					//	Find which child this is
					var lastKey = "";
					for (key in selected._children)
					{
						if (key === rat.profiler.highlightedNode)
						{
							rat.profiler.highlightedNode = lastKey;
							break;
						}
						lastKey = key;
					}
					return true;
				}
				else if (ratEvent.which === rat.keys.k)
				{
					//	Find which child this is
					var useNextKey = false;
					selected = getSelectedNode(rat.profiler.profileTrees[1]);
					for (key in selected._children)
					{
						if (key === rat.profiler.highlightedNode)
							useNextKey = true;
						else if(useNextKey)
						{
							rat.profiler.highlightedNode = key;
							break;
						}
					}
					return true;
				}
			}
			if (ratEvent.which === rat.keys.p)
			{
				rat.profiler.enableStatsDisplay = !rat.profiler.enableStatsDisplay;
				return true;
			}
		}

		return false;
	}

	/**  Add a "profiler mark" to the FPS graph
 */
	rat.profiler.addPerfMarker = function (name, color)
	{
		if( rat.system.debugDrawFramerateGraph && rat.graphics )
		{
			if (rat.graphics)
				perfmarkers.unshift({ name: name, color: color.toString(), frame: rat.graphics.frameIndex });
		}
	};

	/**
	 * Setup the profiler module
	 */
	//@TODO Replace with module registered init function
	rat.profiler.init = function ()
	{
		var self = rat.profiler;
		if (!rat.profiler.enableProfiler)
		{
			self.pushPerfMark = noAction;
			self.popPerfMark = noAction;
		}
		else
		{
			if(rat.system.has.Wraith)
			{
				rat.console.log("Using Wraith Profiler");
				self.pushPerfMark = Wraith.PushPerfMark;
				self.popPerfMark = Wraith.PopPerfMark;
			}
			else if (window.performance && window.performance.now)
			{
				rat.console.log("Using RAT Profiler");
				self.pushPerfMark = ratLevelPushPerfMark;
				self.popPerfMark = ratLevelPopPerfMark;
				var oldBeginFrame = self.beginFrame;
				var oldEndFrame = self.endFrame;
				self.beginFrame = function () { oldBeginFrame(); ratLevelBeginFrame(); };
				self.endFrame = function () { oldEndFrame(); ratLevelEndFrame(); };
				self.displayStats = ratLevelDisplayPerfStats;

				//	Some data setup
				for( var index = 0; index < rat.profiler.profileTrees.length; ++index )
					rat.profiler.profileTrees[index] = new RatProfilerNode("ROOT", void 0);
				rat.profiler.curNode = rat.profiler.profileTrees[0];

				rat.profiler.displayInfo.color = rat.profiler.displayInfo.color || rat.graphics.white;
				rat.profiler.displayInfo.highlightColor = rat.profiler.displayInfo.highlightColor || rat.graphics.yellow;

				rat.input.registerEventHandler(ratLevelEventHandler);
				rat.profiler.usingRatProfiler = true;
			}
			else
			{
				rat.console.log("No Profiler");
				self.pushPerfMark = noAction;
				self.popPerfMark = noAction;
			}
		}
	};

	rat.profiler.beginFrame = function ()
	{
		if( rat.system.debugDrawFramerateGraph && rat.graphics && perfmarkers.length )
		{
			//	Remove frame markers that are now too old
			//	What is our current frame
			var lastValidFrame = rat.graphics.frameIndex - rat.system.FPS_RECORD_SIZE;
			var removeFrom = perfmarkers.length;
			for (var index = perfmarkers.length-1; index >= 0; --index)
			{
				if (perfmarkers[index].frame < lastValidFrame)
					removeFrom = index;
				else
					break;
			}
			if (removeFrom < perfmarkers.length)
				perfmarkers.splice(removeFrom);
		}
	};

	rat.profiler.endFrame = function ()
	{

	};

	rat.profiler.push = function (name)
	{
		var mark = { name: name, last: topMark };
		topMark = mark;
		rat.profiler.pushPerfMark(name);
	};

	rat.profiler.pop = function ()
	{
		var name = topMark.name;
		topMark = topMark.last;
		rat.profiler.popPerfMark(name);
	};

	rat.profiler.perf = function (name, func, ctx)
	{
		rat.profiler.pushPerfMark(name);
		var res;
		if (ctx)
			res = func.call(ctx);
		else
			res = func();
		rat.profiler.popPerfMark(name);
		return res;
	};

});

//--------------------------------------------------------------------------------------------------
//
//	A bunch of general utilities for rat.
//
//	We try to group utilities where it makes sense, like graphics utils go in graphics module,
//	and math utils go in math module,
//	but sometimes it's really just a generically useful function, so it ends up here.
//
rat.modules.add("rat.utils.r_utils",
[
	{ name: "rat.math.r_math", processBefore: true },

	"rat.debug.r_console",
	"rat.os.r_system",
	"rat.os.r_user",
	
	{ name: "rat.utils.r_utils_test", platform: "unitTest" },
],
function (rat)
{
	var math = rat.math;
	//rat.utils = {};  Created as part of r_base

	/** 
	    @constructor
 */
	function _subclassOf() { }	//	used below

	/** 
	   	general inheritance support
	   	Usage:
	   	 declare constructor for class A
	   	 then declare constructor for class B
	   	 then AFTER B constructor function, do this:
	   	 (function(){ Rat.inheritClassFrom(B,A); })();
	   	Also recommended:
	   	 In B constructor, call B.prototype.parentConstructor.call(this);
	    @param {Object} childClass
	    @param {Object} parentClass
	    @return {Object} childClass
	   
 */
	rat.utils.inheritClassFrom = function (childClass, parentClass)
	{
		// Normal Inheritance 
		// this.prototype = new parentClass; // hmm... this instantiates one of the objects.  Do we really want this?

		// Let's try this...  See http://stackoverflow.com/questions/1595611/how-to-properly-create-a-custom-object-in-javascript/1598077#1598077
		// this avoids instantiating an actual new parentClass.
		// instead, it transfers all the prototype values of the base class to a new temp object,
		// and instantiates one of those as our new prototype.
		// This is still weird, but doesn't end up calling parentClass's constructor,
		// which is the key lame thing we want to avoid.
		if (parentClass === void 0)
		{
			rat.console.log("Error inheriting from undefined...");
			var str = childClass.toString();
			str = str.substring(0,1000);
			rat.console.log(str);
			//	and then we'll crash below...
		}
		_subclassOf.prototype = parentClass.prototype;
		childClass.prototype = new _subclassOf();

		childClass.prototype.constructor = childClass;
		childClass.prototype.parentClass = parentClass;
		childClass.prototype.parentConstructor = parentClass;	//	alternative name
		childClass.prototype.parent = parentClass.prototype;
		childClass.prototype.parentPrototype = parentClass.prototype;	//	alternative name
		return childClass;
	};

	/** 
	   	Extend a class with another class (sorta-multi inheritance)
	    @param {Object} childClass
	    @param {Object} parentClass
	   
 */
	rat.utils.extendClassWith = function (childClass, parentClass)
	{
		var parentProto = parentClass.prototype;
		var childProto = childClass.prototype;
		rat.utils.extendObject(childProto, [parentProto], true);
	};

	/** 
	    util to load xml doc
	    @param {string} dname the domain name
	    @return {Object} responseXML The xml response from a get request
	   
 */
	rat.utils.loadXMLDoc = function (dname)
	{
		var xhttp;
		if (window.XMLHttpRequest)
		{
			xhttp = new XMLHttpRequest();
		}
		else
		{
			xhttp = new ActiveXObject("Microsoft.XMLHTTP");
		}
		//xhttp.overrideMimeType('text/xml');
		xhttp.open("GET", rat.system.fixPath(dname), false);
		xhttp.send();
		return xhttp.responseXML;
	};

	/** 
	    util to load json file SYNCHRONOUSLY
	   	Generally, you should use loadJSONObjectAsync instead.
	    @todo refactor with function above - which is more correct?
	    @param {string} dname domain name
	    @return {Object} my_JSON_object
	   
 */
	rat.utils.loadJSONObject = function (dname)
	{
		var my_JSON_object = {};

		var xhttp;
		if (window.XMLHttpRequest)
		{
			xhttp = new XMLHttpRequest();
		}
		else
		{
			xhttp = new ActiveXObject("Microsoft.XMLHTTP");
		}
		// xhttp.overrideMimeType('text/plain');
		xhttp.open("GET", rat.system.fixPath(dname), false);
		xhttp.send();

		my_JSON_object = JSON.parse(xhttp.responseText);

		return my_JSON_object;
	};
	
	/** 
	    util to load json doc asynchronously
	   	Callback will be called with status (true if OK, false if error) and data object.
	   
 */
	rat.utils.loadJSONObjectAsync = function (path, callback)
	{
		var xhttp;
		if (window.XMLHttpRequest)
			xhttp = new XMLHttpRequest();
		else
			xhttp = new ActiveXObject("Microsoft.XMLHTTP");
		xhttp.onload = function(e) {
			if (xhttp.readyState === 4 &&
				(xhttp.status === 200 || (xhttp.status === 0 && xhttp.statusText === "")))
			{
				var data = JSON.parse(xhttp.responseText);
				callback(true, data);
			}
			else {
				rat.console.log("error loading " + path + " : " + xhttp.statusText);
				callback(false, null);
			}
		};

		xhttp.open("GET", rat.system.fixPath(path), true);
		xhttp.send();
	};
	
	/** 
	    Util to load CSS file (async?).
	   	You MUST specifiy an id for this element, since we use that to track if it's already loaded, and to support unloading.
	   	Won't load something already loaded.
	   	No callback system.  You can query if it's loaded by using isCSSLoaded() below
	   	If you *really* want a callback, we could implement a timer system internally that does the query occasionally
	   	and eventually calls the callback.
	   
 */
	rat.utils.loadCSS = function (path, cssID)
	{
		if (document.getElementById(cssID))
			return;
		
		var	fileRef = document.createElement("link");
		fileRef.setAttribute("id", cssID);
		fileRef.setAttribute("rel", "stylesheet");
		fileRef.setAttribute("type", "text/css");
		fileRef.setAttribute("href", path);
		fileRef.setAttribute("media", "all");
		document.getElementsByTagName("head")[0].appendChild(fileRef);
	};
	
	/** 
	   	Unload a CSS file by id.
	   
 */
	rat.utils.unloadCSS = function (cssID)
	{
		var element = document.getElementById(cssID);
		if (element)
			element.parentNode.removeChild(element);
	};

	rat.utils.isCSSLoaded = function(cssID)
	{
		return !!document.getElementById(cssID);
	};

	/** 
	    copy this object
	    useful if you don't want a reference.
	    		Do the recursion and array support here work?
	    @param {Object} o Object to copy
	    @param {boolean=} deep perform a deep copy (recursive) (default is false)
	    @return {Object} newO new Object
	   
 */
	rat.utils.copyObject = function (o, deep)
	{
		// If we got something other than an object (or array), then we cannot really copy it.
		// note that null is technically an object, but should not be processed below, but just returned.
		if (typeof (o) !== "object" || o === null)
			return o;

		deep = deep || false;
		var newO;
		if (Array.isArray(o))
			newO = [];
		else
			newO = {};
		var src;
		for (var e in o)
		{
			src = o[e];
			//	Skip undefined fields
			if (src !== void 0 && o.hasOwnProperty(e))
			{
				// Perform a deep copy if we want to, and need to on this field.
				// We only copy arrays or objects..  NOT functions
				// This also includes constructors.
				if (deep && typeof (src) === "object") // Note that arrays will return their type as Object
					newO[e] = rat.utils.copyObject(src, true);
				else
					newO[e] = src;
			}
		}

		return newO;
	};
	//	alternative name
	rat.utils.deepCopy = function (o) { return rat.utils.copyObject(o, true); }

	/** 
	    Extend one object with another data (possibly doing a deep copy of that data
	    @param {Object} dest
	    @param {Object} sources
	    @param {boolean=} deepCopy (default is false)
	    @return {Object} dest
	   
 */
	rat.utils.extendObject = function (dest, sources, deepCopy)
	{
		deepCopy = deepCopy || false;

		// Always work with an array
		if (Array.isArray(sources) === false)
			sources = [sources];

		var src;
		var source;
		// For each source object, in order.
		for (var index = 0, len = sources.length; index !== len; ++index)
		{
			// These should ALL be objects.
			source = sources[index];
			// Skip undefined sources
			if (source === void 0)
				continue;

			// Skip sources that are the dest
			if (source === dest)
				continue;

			// If dest === void 0, then just run a copy of the source object
			if (dest === void 0 || dest === null)
			{
				dest = rat.utils.copyObject(source, deepCopy);
			}

				// process each field of source
			else
			{
				// For each field in source
				for (var field in source)
				{
					src = source[field];
					// Skip undefined values
					if (src === void 0)
						continue;

					// dest does not exist.
					if (dest[field] === void 0)
					{
						// If src is an object, and we want a deep copy, then copy
						if (typeof (src) === "object" && deepCopy)
							dest[field] = rat.utils.copyObject(src, true);
							// Otherwise, copy the raw types, and point to the objects
						else
							dest[field] = src;
					}
						// dest exists as an object, and src is also an object
						// then make sure my dest's sub objects are not missing any properties
					else if (typeof (dest[field]) === "object" && typeof (src) === "object")
					{
						// Run extend on this object, getting any missing properties
						rat.utils.extendObject(dest[field], [src], deepCopy);
					}
				}
			}
		}
		return dest;
	};
	
	/** 
	   	Util to find all properties of a certain name, set them all to a certain value.
	   	recursively.
	   
 */
	rat.utils.deepSet = function (o, propName, value)
	{
		// If we got something other than an object (or array), then we cannot really work with it.
		if (typeof (o) !== "object" || o === null)
			return;

		for (var e in o)
		{
			if (o.hasOwnProperty(e))
			{
				//	for subtle reasons (replacing a field called "font.font")
				//	we search for objects first and THEN replacement names.
				//	That means this function can't be used to replace objects.
				//	write a new one for that?
				if (typeof(o[e]) === "object")
				{
					rat.utils.deepSet(o[e], propName, value);
				} else if (e === propName)
				{
					o[e] = value;
				}
			}
		}
	};
	
	/** 
	   	Utility function to find an object in an array using a custom compare method
	   	@return {number} index
	   
 */
	rat.utils.searchArrayFor = function( array, testFunc )
	{
		for( var index = 0; index < array.length; ++index )
			if( testFunc( array[index] ) )
				return index;
		return -1;
	};

	/** 
	    Util function to add props to an object
	    @param {Object} obj
	    @param {string} name
	    @param {?} setter
	    @param {?} getter
	    @param {Object=} ops
	    @return {Object} obj
	   
 */
	rat.utils.addProp = function (obj, name, setter, getter, ops)
	{
		var props = rat.utils.extendObject((ops || {}), { enumerable: true, configurable: false });
		if (setter) props.set = setter;
		if (getter) props.get = getter;
		Object.defineProperty(obj || {}, name, props);
		return obj;
	};
	
	/** 
	   	Write this object to a string.
	   	Is this useful?  Consider using JSON utils instead!
	    @param {Object} o
	    @param {string=} name
	    @param {string=} addToString
	    @param {number=} depth
	    @return {string} addToString
	   
 */
	rat.utils.objectToString = function (o, name, addToString, depth)
	{
		if (typeof (addToString) === 'undefined')
			addToString = "";
		if (!depth)
			depth = 0;

		function indent()
		{
			for (var i = 0; i < depth; i++)
				addToString += " ";
		}

		function dumpSingle(val, valName)
		{
			if (typeof (val) === 'undefined')
				return;

			indent();

			if (valName !== '')
			{
				addToString += "" + valName + " : ";

				//if (typeof (val) !== 'undefined')
				// addToString += " : ";
			}

			if (Array.isArray(val))
			{
				// array handling
				addToString += "[\n";

				depth++;
				for (var aIndex = 0; aIndex < val.length; aIndex++)
				{
					dumpSingle(val[aIndex], '');
				}
				depth--;

				indent();
				addToString += "],\n";
			} else if (val === null)
			{
				addToString += "null,\n";
			} else if (typeof (val) === 'function')
			{
				addToString += "/*function*/,\n";
			} else if (typeof (val) === 'object')
			{
				addToString += "{\n";
				addToString = rat.utils.objectToString(val, '', addToString, depth);
				indent();
				addToString += "},\n";
			} else if (typeof (val) === 'string')
			{
				addToString += "'" + val.toString() + "'" + ",\n";
			} else if (typeof (val) === 'undefined')
			{
				addToString += "x_x_undefined,\n";
			} else
			{
				addToString += val.toString() + ",\n";
			}
		}

		if (name !== '') // name passed in - currently this means top level...?
		{
			indent();
			addToString += name + " = {\n";
		}
		depth++;
		for (var e in o)
		{
			if (o.hasOwnProperty(e))
			{
				dumpSingle(o[e], e);
			}
		}
		depth--;
		if (name !== '')
		{
			indent();
			addToString += "};\n";
		}

		return addToString;
	};

	/*
	//	testing objectToString
	var xx = {name:"hey", count:12, subObject:{x:1, y:null}, ar: [1, 2, 'str'], ar2: [{u:1, v:2}, [8,9,0]]};
	var outxx = rat.utils.objectToString(xx, "xx");
	rat.console.log("----");
	rat.console.log(outxx);
	rat.console.log("----");
	*/

	// These utils get and set values deep in an object structure
	// e.g.:  rat.utils.set(bob, 'home.kitchen.sink', 'yes') is like bob.home.kitchen.sink = 'yes';
	//	and rat.utils.get(bob, 'home.kitchen.sink') returns the final "sink" value (or object).

	/** 
	    get a deep value in an object, returning undefined if the desired path doesn't match the object
	    @param {Object} obj
	    @param {string} path
	    @return {?} value deep value in object 
	   
 */
	rat.utils.get = function (obj, path)
	{
		path = path.split('.');
		var origContext = obj;
		for (var i = 0, len = path.length; i < len; i++)
		{
			//	todo: maybe support calling named functions, if path[i] is something like "func()"?
			//		but at that point this could be eval()...
			
			var  part = path[i];
			
			//	check for array reference - mostly just useful in rat console right now?
			if (part.substring(part.length-1) === "]")
			{
				var leftBracket = part.indexOf("[");
				var subPart = part.substring(leftBracket+1, part.length-1);
				//	what kind of subscript is this?  just a number?
				var index = parseInt(subPart, 10);
				if (isNaN(index))
				{
					//	we don't have a way of dealing with complex cases here, like
					//	thing[other.value]
					//	and even if we did, we would have incorrectly divided things up with our split call above.
					index = 0;
				}
				var refPart = part.substring(0, leftBracket);
				obj = obj[refPart];	//	refer to the array part
				part = index;	//	and let the code below index it.
			}
			
			//	get next subobject
			obj = obj[part];
			if (typeof (obj) === 'undefined')
				return obj;	//	undefined
		}

		return obj;
	};

	/** 
	    set a deep value in an object, adding subobject structure if necessary
	    @param {Object} obj
	    @param {string} path
	    @param {?} value deep value in object to set
	   
 */
	rat.utils.set = function (obj, path, value)
	{
		path = path.split('.');
		for (var i = 0, len = path.length; i < len - 1; i++)
		{
			var sobj = obj[path[i]];
			//	handle the case where the structure doesn't exist yet - add subobjects as necessary
			if (typeof (sobj) === 'undefined' || sobj === null)
				sobj = obj[path[i]] = {};
			obj = sobj;
		}

		obj[path[len - 1]] = value;
	};

	/** 
	    given an interpolation value from 0 to 1, find the appropriate blend of a and b
	    @param {number} a
	    @param {number} b
	    @param {number} i [0.0, 1.0]
	    @return {number} interpolated_value [a, b]
	   
 */
	rat.utils.interpolate = function (a, b, i)
	{
		return b * i + (1 - i) * a;
	};
	/** 
	    given an interpolation value from 0 to 1, find the appropriate blend of a and b
	   	and clamp to [0,1]
	    @param {number} a
	    @param {number} b
	    @param {number} i [0.0, 1.0]
	    @return {number} interpolated_value [a, b]
	   
 */
	rat.utils.interpolateClamped = function (a, b, i)
	{
		if (i < 0) i = 0;
		if (i > 1) i = 1;
		return b * i + (1 - i) * a;
	};

	/** 
	    given an interpolation value from 0 to 1, find the appropriate point between A and B
	    @param {Object} posA
	    @param {Object} posB
	    @param {number} [0.0, 1.0]
	    @return {Object} interpolated_position [posA, posB]
	   
 */
	rat.utils.interpolatePos = function (posA, posB, i)
	{
		return { x: posB.x * i + (1 - i) * posA.x, y: posB.y * i + (1 - i) * posA.y, };
	};

	/** 
	    limit to these values, inclusive
	    @param {number} x
	    @param {number} min
	    @param {number} max
	    @return {number} x [min, max]
	   
 */
	rat.utils.limit = function (x, min, max)
	{
		// could use Math.max and Math.min, but that'd be more function calls, which we like to avoid
		if (x < min) return min;
		if (x > max) return max;
		return x;
	};

	/** 
	   	reorder this list randomly
	    @param {Array} list
	    @return {Array} list
	   
 */
	rat.utils.randomizeList = function (list, rng)
	{
		if (!rng)
			rng = Math;
		for (var i = 0; i < list.length; i++)
		{
			var targ = Math.floor(rng.random() * list.length);
			var temp = list[i];
			list[i] = list[targ];
			list[targ] = temp;
		}
		return list;
	};

	/** 
	    return random entry in this array (pick random)
	    if a "skip" is provided, avoid that entry when picking a random entry.
	    (skip can be an individual index, or an array of indices to skip, or not provided)
	    @param {Array} list
	    @param {Array|number=} skip
	    @return {?} list_item
	   
 */
	rat.utils.randomInList = function (list, skip)
	{
		if (skip === void 0) // not specified
		{
			// quick easy version, let's just do that now.
			var targ = Math.floor(Math.random() * list.length);
			return list[targ];
		}

		// more complex version supporting skip
		if (Array.isArray(skip) === false)
			skip = [skip];
		skip.sort(function (a, b)
		{
			return b - a;
		});
		var len = list.length - skip.length;
		var targ = Math.floor(Math.random() * len);
		for (var index = 0; index !== skip.length; ++index)
			if (skip[index] <= targ)
			{
				++targ;
			}
		return list[targ];
	};

	/** 
	    Get a random number between a certain range
	    TODO: make sure these do what we want...
	    @param {number} min
	    @param {number} max
	    @return {number} random_number
	   
 */
	rat.utils.randomInRange = function (min, max)
	{
		var difference = max - min;
		return (Math.random() * difference) + min;
	};

	/** 
	    Get a random whole number between a certain range
	    limit to these values, inclusive
	    @param {number} min
	    @param {number} max
	    @return {number} random_number
	   
 */
	rat.utils.randomIntInRange = function (min, max)
	{
		var difference = max - min;
		return Math.round(Math.random() * difference) + min;
	};

	/** 
	    utility to make a random ID out of characters and numbers
	    @param {length} len
	    @return {string} text
	   
 */
	rat.utils.makeID = function (len)
	{
		var text = "";
		var possible = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

		for (var i = 0; i < len; i++)
			text += possible.charAt(Math.floor(Math.random() * possible.length));

		return text;
	};
	
	/** 
	   	Utility to make a unique player/system ID
	   	At least, we try pretty hard.  Not guaranteed to be unique.
	   
 */
	rat.utils.makeUniqueID = function()
	{
		var d = new Date();
		var prefix = rat.utils.encodeID(d.getTime());
		return prefix + '_' + rat.utils.makeID(5);	//	plus some randomness
	};
	
	/** 
	   	utility to encode a number into a shorter alphanumeric ID
	   
 */
	rat.utils.encodeID = function (value)
	{
		var possible = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
		//var possible = "0123456789";	(just a test: this results in exact itoa value)
		
		var div = possible.length;
		
		var text = "";
		var left = value;
		do {
			var part = left % div;
			left = Math.floor(left / div);	//	value may be huge, so use real floor, not bitwise operators
			text = possible.charAt(part) + text;
		} while(left);
		return text;
	};

	/** 
	    get current browser window size
	    this is usually what we want - just usable space, not including things like menu bar or chrome debugger panel
	    @suppress {missingProperties}
	    @return {Object} windowSize (w, h | x, y)
	   
 */
	rat.utils.getWindowSize = function ()
	{
		// http://stackoverflow.com/questions/3437786/how-to-get-web-page-size-browser-window-size-screen-size-in-a-cross-browser-wa
		var w = window;
		var d = document;
		var e = d.documentElement;
		if (!e && d.getDocumentElement)
			e = d.getDocumentElement();
		var g = {};
		if (d.getElementsByTagName)
			g = d.getElementsByTagName('body')[0];
		var x = w.innerWidth || e.clientWidth || g.clientWidth;
		var y = w.innerHeight || e.clientHeight || g.clientHeight;

		//rat.console.log("------------------------");
		//rat.console.log("Window Size Info:");
		//rat.console.log("ch = " + e.clientHeight);

		/*
		  STT: sometimes we're one pixel too big.  Detect this by watching clientheight,
		  but don't use that value, since it's a scrollbar size sometimes?
		  Pretty dang kludgey, but fixes a scaling problem in chrome...
		  STT UPDATE 2015.01.04:  A much better fix is to add this to your css:
		  
		  body {
		  overflow-y:hidden;
		  }
		  
		  I tried various other things, like border-box sizing and setting every margin/border to 0,
		  and they didn't work because... who the heck knows?  HTML and CSS suck grandma donkey feet.
		  
		  http://stackoverflow.com/questions/14673912/remove-vertical-scrollbar-that-is-clearly-not-necessary
		  http://www.paulirish.com/2012/box-sizing-border-box-ftw/
		  http://w3schools.invisionzone.com/index.php?showtopic=40135
		  
		  anyway, here's what we used to be doing here in code,
		  which sometimes seemed to help (e.g. on initial load) but not always (e.g. on later resize)
		  
		  if (rat.system.has.chromeBrowser && e.clientHeight && w.innerHeight && e.clientHeight < w.innerHeight)
		  y = w.innerHeight-1;
		  
		  //	Same thing, but with width - this has been commented out for a while - not recommended?
		  //if (e.clientWidth && w.innerWidth && e.clientWidth < w.innerWidth)
		  //	x = e.clientWidth;
		  */

		return { x: x, y: y, w: x, h: y };	//	so caller can use x,y or w,h names
	};

	/** 
	    Dynamic script loading support
	    Note that for this to work in chrome locally, you might need to launch chrome with --allow-file-access-from-files.
	    That won't be a problem for a version of the game hosted on line.
	   
	    This is the function you generally want to use:
	    Pass in either a single filename or an array of files to load
	    @param {string|Array.<string>} scripts the scripts to load
	    @param {function()=} clientCallback
	    @param {Object=} options  {async = true}
	   
 */
	rat.utils.loadScripts = function (scripts, clientCallback, options)
	{
		if (!clientCallback)
			clientCallback = void 0;
		if (!Array.isArray(scripts))
			scripts = [scripts];
		var async = true;
		if (options && options.async !== void 0)
			async = options.async || false;

		//	add these all to the global script list first, so we know how many we're going to be waiting for.
		//		(if we add and load at the same time, it's possible for one script to immediately load,
		//		and the client will call allScriptsAreLoaded and see that 1/1 scripts are loaded, and move on,
		//		even if several scripts were passed in here.
		var startCount = rat.utils.scripts.length;
		var curCount = startCount;
		var i;
		for (i = 0; i < scripts.length; i++)
		{
			//	add to my list.
			//	first filter out duplicates!  This happens sometimes (accidentally, but commonly) so we need to protect against it.
			if (rat.utils.scriptIndex(scripts[i]) >= 0)
			{
				rat.console.log("warning:  already loaded script " + scripts[i]);
				continue;
			}
			rat.utils.scripts[curCount++] = { filename: scripts[i], state: 'none', type: 'script', clientCallback: clientCallback };
		}

		//	THEN trigger the loads for those we just added, with our standard dynamicLoadCompleteCallback function
		// No, if a script adds other scripts this will fail spectacularly. Need to only run on the scripts we actually just added
		for (i = startCount; i < curCount; i++)
		{
			// console.log("starting loadScriptWithCallback: " + rat.utils.scripts[i].filename);
			rat.utils.loadScriptWithCallback(rat.utils.scripts[i].filename, async, rat.utils.dynamicLoadCompleteCallback);
		}
	};

	//	list of scripts/data we should be loading or should have loaded.
	rat.utils.scripts = [];
	rat.utils.scriptsLoadedCount = 0;	//	count of successful loads

	//	let's piggyback on that to read JSON files, too.
	//	Add to the same list so the client can wait generally for requested JSON files to be done.

	/** 
	    load one JSON file with this callback on success
	    This is the function clients should use, one at a time for desired JSON files.
	    This is different from the above, I know.  The thing is, each time we load a JSON file, it needs to get assigned to
	    a variable, which implies the completion callback really needs to be unique per item...
	    e.g. rat.utils.loadJSON("mydata.json", function(data) { myvar = data; });
	    @param {string} filename
	    @param {function} completeCallback
	    @param {boolean} stripComments
	   
 */
	rat.utils.loadJSON = function (filename, completeCallback, stripComments)
	{
		var completeWrapper = function (unparsedData)
		{
			//	I hate JSON for not supporting comments.  So we're going to support them, dang it.
			if (stripComments && unparsedData)
			{
				//	see http://stackoverflow.com/questions/5989315/regex-for-match-replacing-javascript-comments-both-multiline-and-inline
				unparsedData = unparsedData.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '$1');
			}
			var parsed = unparsedData ? JSON.parse(unparsedData) : {};
			completeCallback(parsed);
		};

		rat.utils.getResource(filename, completeWrapper, 'json');
	};

	/** 
	    Loads an XML file
	    @param {string} filename
	    @param {function} completeCallback
	   
 */
	rat.utils.loadXML = function (filename, completeCallback)
	{
		// not neccesary here as maybe it is more platform specific
		// but in some cases you may want to parse the XML data using the following code.
		//var parser = new DOMParser();
		//var xmlData = parser.parseFromString(xmlData, "text/xml");

		rat.utils.getResource(filename, completeCallback, 'xml');
	};


	// PMM - refactoring out so we can make this usable by more than just JSON

	/** 
	    generic function that will go out and get a file of any type
	   	it does use the script system callback to make sure all file loads are complete
	    @param {string} filename
	    @param {function} completeCallback
	    @param {string} fileType
	   
 */
	rat.utils.getResource = function (filename, completeCallback, fileType)
	{
		rat.utils.scripts[rat.utils.scripts.length] = { filename: filename, state: 'none', type: fileType };

		var xhr = new XMLHttpRequest();

		// not really sure this is platform independent at this point.
		// Maybe use jquery.  :)
		var complete = function ()
		{
			if (xhr.readyState === 4)
			{
				rat.utils.dynamicLoadCompleteCallback(filename); // mark loaded for tracking
				completeCallback(xhr.responseText);
			}
		};
		xhr.onreadystatechange = complete; // IE?  (actually this is the standard and onload is not -pkk)
		//xhr.onload = complete; // others

		xhr.open("GET", rat.system.fixPath(filename), true);
		//console.log("FILE: " + filename);
		try
		{
			xhr.send();
		}
		catch (exception)
		{ }
	};

	/** 
	    return index of script in list to load, or -1 if not found
	    @param {string} filename
	    @return {number} j
	   
 */
	rat.utils.scriptIndex = function (filename)
	{
		for (var j = 0; j < rat.utils.scripts.length; j++)
		{
			if (rat.utils.scripts[j].filename === filename)
				return j;
		}
		return -1;
	};

	/** 
	    callback when a script is loaded
	    @param {string} filename
	   
 */
	rat.utils.dynamicLoadCompleteCallback = function (filename)
	{
		//	todo: check ready state to really see if it's loaded?
		var index = rat.utils.scriptIndex(filename);
		if (index >= 0)
		{
			var entry = rat.utils.scripts[index];
			if (entry.state !== 'done')
			{
				entry.state = 'done';
				rat.utils.scriptsLoadedCount++;

				if (entry.clientCallback)
					entry.clientCallback(filename);
			}
		}
	};

	/** 
	    returns true if all queued up script loads are done
	    @return {bool} loaded
	   
 */
	rat.utils.allScriptsAreLoaded = function ()
	{
		var count = rat.utils.scripts.length;
		if (rat.utils.scriptsLoadedCount === count)
		{
			//console.log("dynamic scripts done: " + rat.utils.scripts.length);
			return true;
		} else
		{
			//console.log("dynamic script load: waiting for " + (rat.utils.scripts.length-rat.utils.scriptsLoadedCount) + " / " + rat.utils.scripts.length);
			//for (var j = 0; j < rat.utils.scripts.length; j++)
			//{
			//	if (rat.utils.scripts[j].state !== 'done')
			//		rat.console.log("-->" + rat.utils.scripts[j].filename);
			//}
			return false;
		}
	};

	/** 
	    Launches Help UI
	    @param forUserID
	   
 */
	rat.utils.launchSystemHelpUI = function (forUserID)
	{
		if (rat.system.has.xbox)
		{
			rat.console.log("Launching system help ui for the Xbox");
			var user = rat.user.getUser(forUserID);
			if (user)
				window.Windows.Xbox.ApplicationModel.Help.show(user.rawData);
		}
	};
	
	/** 
	   	Grab filename without path (strip path from filename)
	   
 */
	rat.utils.stripPath = function(path)
	{
		var end = path.lastIndexOf("/");
		var end2 = path.lastIndexOf("\\");
		if (end2 > end)
			end = end2;
		if (end < 0)	//	no path, just return original name
			return path;
		return path.substring(end+1);
	};
	
	/** 
	   	Grab just the path from a filename
	   
 */
	rat.utils.getPath = function(path, includeFinalSlash)
	{
		if (includeFinalSlash === void 0)
			includeFinalSlash = true;
		
		var end = path.lastIndexOf("/");
		var end2 = path.lastIndexOf("\\");
		if (end2 > end)
			end = end2;
		if (end < 0)	//	no path
			return "";//includeFinalSlash ? "/" : "";
		if (includeFinalSlash)
			end++;
		return path.substring(0, end);
	};
	
	/** 
	   	Clean up a path name.  collapse ../ references and ./ references and fix slashes.
	   	Note that we can only collapse ../ references if there's a path above that in the string.
	   	Also, if a ../ reference comes before another ../ reference, we can't do much about it.
	   	So, we clean up from left to right as much as we can, but we can't guarantee no ../ when we're done.
	   
	   	Returns cleaned path.
	   
 */
	rat.utils.cleanPath = function(path)
	{
		//	First, change \\ to /
		while( path.indexOf( "\\" ) >= 0 )
			path = path.replace( "\\", "/" );
		
		//	Find all // and change to /
		while( path.indexOf( "//" ) >= 0 )
			path = path.replace( "//", "/" );
		
		//	From the begining of the string, find the first
		var found = true;
		var fromIdx = 0;
		while( found )
		{
			found = false;
			
			//	../
			//		If this is preceded by a director (DIR/../) remove all but a /
			var idx1 = path.indexOf( "/../", fromIdx );
					
			//	./	Just remove (DIR/./ DIR)		
			var idx2 = path.indexOf( "/./", fromIdx );
			if( idx1 >= 0 && (idx2 < 0 || idx1 <= idx2) )
			{
				found = true;
				fromIdx = idx1 + 3;
				var prevPath = path.substring( 0, idx1 ).lastIndexOf( "/");
				if( prevPath >= 0 )
				{
					path = path.substring( 0, prevPath) + path.substring( idx1 + 3);
					
				}
			}
			
			if( !found && idx2 >= 0 )
			{
				found = true;
				fromIdx = idx2 + 2;
				path.splice( idx2, 2 );
			}
		}
		
		return path;
	};
	
	//	format seconds in text nicely.
	//	todo:
	//		take formatting arguments
	//		support longer times
	//		support fractions of second (especially when under 10s or something),
	//		etc.
	rat.utils.secondsToText = function(seconds)
	{
		var text;
		
		var minutes = (seconds/60)|0;
		seconds -= minutes * 60;
		seconds = seconds | 0;
		
		var secString;
		if (seconds < 10)
			secString = "0" + seconds;
		else
			secString = seconds;
		text = "" + minutes + ":" + secString;
		
		return text;
	};
	
	//	format time string
	//	e.g. 06:32:05 pm
	rat.utils.dateToTimeText = function(dateObject, use24hour, includeSeconds)
	{
		var h = dateObject.getHours();
		var m = dateObject.getMinutes();
		var s = dateObject.getSeconds();
		
		var suffix = "";
		if (!use24hour)
		{
			if (h >= 12)
			{
				h -= 12;
				suffix = " pm";
			} else {
				suffix = " am";
			}
		}

		var text;
		if (h < 10)
			text = "0" + h;
		else
			text = "" + h;
		
		if (m < 10)
			text += ":0" + m;
		else
			text += ":" + m;
		
		if (includeSeconds)
		{
			if (s < 10)
				text += ":0" + s;
			else
				text += ":" + s;
		}
		
		text += suffix;
		
		return text;
	};
	
});
//--------------------------------------------------------------------------------------------------
//
//	Rat Math library
//
/** 
    Basic math functions.
   	Wrapper around the gobal Math object (John argues this is faster).
   	and some unique convenient math functions.
   
 */
rat.modules.add("rat.math.r_math",
[],
function (rat)
{
	/** 
	    Namespace for math functions
	    @namespace
	   
 */
	rat.math = {};

	// Some constants

	/**  @const 
 */
	rat.math.PI = Math.PI;
	/**  @const 
 */
	rat.math.PI2 = Math.PI * 2.0;
	/**  @const 
 */
	rat.math.TAU = Math.PI * 2.0;
	/**  @const 
 */
	rat.math.HALFPI = Math.PI / 2.0;
	/**  @const 
 */
	rat.math.E = Math.E;
	/**  @const 
 */
	rat.math.MAX_NUMBER = Number.MAX_VALUE;
	/**  @const 
 */
	rat.math.MIN_NUMBER = Number.MIN_VALUE;
	/**  @const 
 */
	rat.math.DegreesToRadians = rat.math.PI / 180.0;
	rat.math.RadiansToDegrees = 180.0 / rat.math.PI;

	// Basic functions that we get from the built in math library
	rat.math.abs = Math.abs;
	rat.math.min = Math.min;
	rat.math.max = Math.max;
	rat.math.ceil = Math.ceil;
	rat.math.floor = Math.floor;
	rat.math.round = Math.round;

	rat.math.cos = Math.cos;
	rat.math.sin = Math.sin;
	rat.math.tan = Math.tan;
	rat.math.acos = Math.acos;
	rat.math.asin = Math.asin;
	rat.math.atan = Math.atan;
	rat.math.atan2 = Math.atan2;

	rat.math.random = Math.random;
	rat.math.sqrt = Math.sqrt;
	rat.math.log = Math.log;
	rat.math.exp = Math.exp;
	rat.math.pow = Math.pow;

	/** 
	    random value min >= value >= max
	   
 */
	rat.math.randomRange = function(min, max)
	{
		return (rat.math.random() * (max-min)) + min;
	};
	
	/** 
	    Clamps to make sure that low <= a <= high
	    @param {number} a the given value
	    @param {number} low the low clamp level
	    @param {number} high the high clamp level
	    @return {number} a the clamped value
	   
 */
	rat.math.clamp = function (a, low, high)
	{
		if (a < low)
			return low;
		else if (a > high)
			return high;
		return a;
	};

	/** 
	    Interpolate between two numbers
	    interp = 0 -> valA | interp = 1 -> valB
	    @param {number} valA 
	    @param {number} valB 
	    @param {number} interp 0.0 - 1.0 
	    @return {number} [valA, valB]
	   
 */
	rat.math.interp = function (valA, valB, interp)
	{
		return valB * interp + valA * (1.0 - interp);
	};

	/** 
	    Get the sign of a number
	    @param {number} num
	    @return {number} sign -1, 0, 1
	   
 */
	rat.math.signOf = function (num)
	{
		return (num > 0) ? 1 : ((num < 1) ? -1 : 0);
	};

	/** 
	    Return a variance of +- v
	    @param {number} v
	    @returns {number} [-v, v]
	   
 */
	rat.math.randomVariance = function (v)
	{
		if (!v)
			return 0;
		return v * 2 * rat.math.random() - v;
	};

	/** 
	    Finds the center of a circle.
	    @param {number} x1 x-coordinate of a point on the circle.
	    @param {number} y1 y-coordinate of a point on the circle.
	    @param {number} x2 x-coordinate of the other point on the circle.
	    @param {number} y2 y-coordinate of the other point on the circle.
	    @param {number} radius of the circle.
	    @param {?number} centerDirectionX The desired direction of the center on the x-axis. Defaults to 1.
	    @param {?number} centerDirectionY The desired direction of the center on the y-axis. Defaults to 1.
	    @return {{x:number, y:number}} The point of the circle's center.
	   
 */
	rat.math.findCircleCenterFromTwoPoints = function (x1, y1, x2, y2, radius, centerDirectionX, centerDirectionY)
	{
		// Find the center of the circle.
		// Based on the formula at: http://mathforum.org/library/drmath/view/53027.html
		var dx = x2 - x1;
		var dy = y2 - y1;
		var lineSegmentDistance = Math.sqrt(dx * dx + dy * dy);
		var midX = (x2 + x1) * 0.5;
		var midY = (y2 + y1) * 0.5;
		var distanceFromMidpoint = Math.sqrt(radius * radius - lineSegmentDistance * lineSegmentDistance * 0.25);

		// Figure out how we want to treat the signs based on the desired direction we want it to be in.
		// First, consider the center in the direction of <dy, -dx>.
		// The dot product with the desired direction is positive if they are both in the same general direction.
		var perpendicularDx = dy;
		var perpendicularDy = -dx;
		var dotProductCenterDirection = perpendicularDx * centerDirectionX - perpendicularDy * centerDirectionY;
		if (dotProductCenterDirection < 0)
		{
			perpendicularDx = -dy;
			perpendicularDy = dx;
		}

		var centerX = midX + distanceFromMidpoint * perpendicularDx / lineSegmentDistance;
		var centerY = midY + distanceFromMidpoint * perpendicularDy / lineSegmentDistance;
		return { x: centerX, y: centerY };
	};

	/** 
	    Finds the center of a circle.
	    @param {number} x x-coordinate of a point on the circle.
	    @param {number} y y-coordinate of a point on the circle.
	    @param {number} centerX x-coordinate of the circle's center.
	    @param {number} centerY y-coordinate of the circle's center.
	    @return {number} The angle of the circle from the x-axis in radians.
	   
 */
	rat.math.findAngleOnCircle = function (x, y, centerX, centerY)
	{
		var offsetX = x - centerX;
		var offsetY = y - centerY;
		return rat.math.atan2(offsetY, offsetX);
	};

	/** 
	    Finds the arc which passes through two points.
	    @param {number} x1 x-coordinate of a point on the circle.
	    @param {number} y1 y-coordinate of a point on the circle.
	    @param {number} x2 x-coordinate of the other point on the circle.
	    @param {number} y2 y-coordinate of the other point on the circle.
	    @param {number} centerX  x-coordinate of the circle's center.
	    @param {number} centerY  y-coordinate of the circle's center.
	    @param {number} radius of the circle.
	    @return {{type:string, center:{x:number, y:number}, radius:number, startAngle:number, endAngle:number, anticlockwise:boolean}} Represents the arc.
	   
 */
	rat.math.findArcOnCircle = function (x1, y1, x2, y2, centerX, centerY, radius)
	{
		// Find if the arc goes clockwise or anticlockwise.
		// Check the z-coordinates of the cross product of p1 to center and p1 to p2.
		var anticlockwise = (centerX - x1) * (y2 - y1) - (centerY - y1) * (y2 - x1) > 0;

		var startAngle = rat.math.findAngleOnCircle(x1, y1, centerX, centerY);
		var endAngle = rat.math.findAngleOnCircle(x2, y2, centerX, centerY);

		return {
			type: "arc",
			center: { x: centerX, y: centerY },
			radius: radius,
			startAngle: startAngle,
			endAngle: endAngle,
			anticlockwise: anticlockwise
		};
	};

	/** 
	    Finds the arc which passes through two points.
	    @param {number} x1 x-coordinate of a point on the circle.
	    @param {number} y1 y-coordinate of a point on the circle.
	    @param {number} x2 x-coordinate of the other point on the circle.
	    @param {number} y2 y-coordinate of the other point on the circle.
	    @param {number} radius of the circle.
	    @param {?number} centerDirectionX The desired direction of the center on the x-axis. Defaults to 1.
	    @param {?number} centerDirectionY The desired direction of the center on the y-axis. Defaults to 1.
	    @return {{center:{x:number, y:number}, radius:number, startAngle:number, endAngle:number, anticlockwise:boolean}} Represents the arc.
	   
 */
	rat.math.findArcFromTwoPoints = function (x1, y1, x2, y2, radius, centerDirectionX, centerDirectionY)
	{
		var center = rat.math.findCircleCenterFromTwoPoints(x1, y1, x2, y2, radius, centerDirectionX, centerDirectionY);
		return rat.math.findArcOnCircle(x1, y1, x2, y2, center.x, center.y, radius);
	};

	/** 
	    Finds the perpendicular bisector of two points.
	    @param {number} x1 x-coordinate of point 1 on the circle.
	    @param {number} y1 y-coordinate of point 1 on the circle.
	    @param {number} x2 x-coordinate of point 2 on the circle.
	    @param {number} y2 y-coordinate of point 2 on the circle.
	    @return {{a:number, b:number, c:number}} the perpendicular bisector in the form of ax+by=c.
	   
 */
	rat.math.findPerpendicularBisector = function (x1, y1, x2, y2)
	{
		var dx = x2 - x1;
		var dy = y2 - y1;
		var midX = (x2 + x1) * 0.5;
		var midY = (y2 + y1) * 0.5;

		if (dy === 0)
			// The perpendicular bisector is vertical.
			return { a: 1, b: 0, c: midX };

		var slope = -dx / dy;// perpendicular slope
		return { a: -slope, b: 1, c: -slope * midX + midY };
	};

	/** 
	    Finds the center of a circle.
	    @param {number} x1 x-coordinate of point 1 on the circle.
	    @param {number} y1 y-coordinate of point 1 on the circle.
	    @param {number} x2 x-coordinate of point 2 on the circle.
	    @param {number} y2 y-coordinate of point 2 on the circle.
	    @param {number} x3 x-coordinate of point 3 on the circle.
	    @param {number} y3 y-coordinate of point 3 on the circle.
	    @return {{center:{x:number, y:number}, radius:number}|boolean} The point of the circle's center or false if the points are a strait line.
	   
 */
	rat.math.findCircleFromThreePoints = function (x1, y1, x2, y2, x3, y3)
	{
		// The center of the circle is at the intersection of the perpendicular bisectors.
		var line1 = rat.math.findPerpendicularBisector(x1, y1, x2, y2);
		var line2 = rat.math.findPerpendicularBisector(x1, y1, x3, y3);

		// Use line1 and line2 to eliminate y.
		var line3 = void 0;
		if (line1.b === 0)
			line3 = line1;
		else if (line2.b === 0)
			line3 = line2;
		else
		{
			// Eliminate y
			var lineBScalar = -line1.b / line2.b;
			line3 = {
				a: line1.a + line2.a * lineBScalar,
				//b: line1.b + line2.b * lineBScalar, // b should be zero.
				c: line1.c + line2.c * lineBScalar,
			};
		}
		if (line3.a === 0)
			// x was eliminated with y, so the lines must be parallel.
			return false;

		var x = line3.c / line3.a;
		var y = (line1.b !== 0) ? // Solve for y in the equation with y
		(line1.c - line1.a * x) / line1.b :
			(line2.c - line2.a * x) / line2.b;

		// Find the radius
		var dx = x1 - x;
		var dy = y1 - y;
		var radius = rat.math.sqrt(dx * dx + dy * dy);

		return {
			center: { x: x, y: y },
			radius: radius
		};
	};

	/** 
	    Finds the arc which passes through three points. The ends are at point 1 and point 3.
	    @param {number} x1 x-coordinate of point 1 on the circle.
	    @param {number} y1 y-coordinate of point 1 on the circle.
	    @param {number} x2 x-coordinate of point 2 on the circle.
	    @param {number} y2 y-coordinate of point 2 on the circle.
	    @param {number} x3 x-coordinate of point 3 on the circle.
	    @param {number} y3 y-coordinate of point 3 on the circle.
	    @return {{type:string, center:{x:number, y:number}, radius:number, startAngle:number, endAngle:number, anticlockwise:boolean}|{type:string, point1:{x:number, y:number},point2:{x:number, y:number}}} Represents the arc or strait line if the three points line up.
	   
 */
	rat.math.findArcFromThreePoints = function (x1, y1, x2, y2, x3, y3)
	{
		var circle = rat.math.findCircleFromThreePoints(x1, y1, x2, y2, x3, y3);
		if (!circle)
			return {
				type: "seg",
				point1: { x: x1, y: y1 },
				point2: { x: x3, y: y3 },
			};
		return rat.math.findArcOnCircle(x1, y1, x3, y3, circle.center.x, circle.center.y, circle.radius);
	};
});
//--------------------------------------------------------------------------------------------------
/** 
   	A vector class and some related 2d geometry functions
   
   ------------ rat.Vector ----------------
 */
rat.modules.add("rat.math.r_vector",
[
	{ name: "rat.math.r_math", processBefore: true },
],
function (rat)
{
	//var math = rat.math;

	/** 
	    standard 2D Vector
	    @constructor
	    @param {number|Object=} x rat.Vector
	    @param {number=} y
	   
 */
	rat.Vector = function (x, y)
	{
		if (x !== void 0 && x.x !== void 0)
		{
			y = x.y;
			x = x.x;
		}
		this.x = x || 0;
		this.y = y || 0;
	};

	/** 
	    Makes a copy of this vector and returns it
	    @return {Object} newVec rat.Vector
	   
 */
	rat.Vector.prototype.copy = function ()
	{
		var newVec = new rat.Vector();
		newVec.x = this.x;
		newVec.y = this.y;
		return newVec;
	};

	/**  
	    Sets a vector equal to another vector, or to param x, y values 
	    @param {Object|number} x rat.Vector
	    @param {number=} y 
	   
 */
	rat.Vector.prototype.set = function (x, y)
	{
		// Paul wants this to say that this duplicates the copyFrom behavior
		if (x.x !== void 0)
		{
			y = x.y;
			x = x.x;
		}
		this.x = x;
		this.y = y;
	};

	/** 
	    Sets this vector to be the same as the input vector
	    @param {Object} vec rat.Vector
	   
 */
	rat.Vector.prototype.copyFrom = function (vec)
	{
		this.x = vec.x;
		this.y = vec.y;
	};

	/** 
	    Returns the length of the vector
	    @return {number} length
	   
 */
	rat.Vector.prototype.length = function ()
	{
		return rat.math.sqrt(this.x * this.x + this.y * this.y);
	};

	/** 
	    Returns the the length of the vector squared
	    @return {number} length_squared
	   
 */
	rat.Vector.prototype.lengthSq = function ()
	{
		return this.x * this.x + this.y * this.y;
	};

	/** 
	    Returns the distance squared from this vector to another
	    @param {Object} v rat.Vector
	    @return {number} distance_squared
	   
 */
	rat.Vector.prototype.distanceSqFrom = function (v)
	{
		var dx = v.x - this.x;
		var dy = v.y - this.y;
		return dx * dx + dy * dy;
	};

	/** 
	    Returns the distance from this vector to another
	    @param {Object} v rat.Vector
	    @return {number} distance
	   
 */
	rat.Vector.prototype.distanceFrom = function (v)
	{
		return rat.math.sqrt(this.distanceSqFrom(v));
	};

	/** 
	    Get a delta vector from this to v
	    @param {Object} v rat.Vector
	    @param {Object=} dest rat.Vector
	    @return {Object} delta rat.Vector
	   
 */
	rat.Vector.prototype.deltaTo = function (v, dest)
	{
		dest = dest || new rat.Vector();
		dest.set(v.x - this.x, v.y - this.y);
		return dest;
	};

	/** 
	    Normalizes the vector (Scales to 1)
	   
 */
	rat.Vector.prototype.normalize = function ()
	{
		var mag = this.length();
		this.x /= mag;
		this.y /= mag;
	};

	/** 
	    Multiplies the vector by a constant scaler
	    @param {number} newScale
	   
 */
	rat.Vector.prototype.scale = function (newScale)
	{
		this.x *= newScale;
		this.y *= newScale;
	};

	/** 
	    Scales the vector to be a specific length
	    @param {number} newLength
	   
 */
	rat.Vector.prototype.setLength = function (newLength)
	{
		this.normalize();
		this.scale(newLength);
	};

	/** 
	    Adds another vector to this one
	    @param {Object} vec rat.Vector
	   
 */
	rat.Vector.prototype.add = function (vec)
	{
		this.x += vec.x;
		this.y += vec.y;
	};

	/** 
	    Multiplies vec by scale and adds it to this vector
	    @param {Object} vec rat.Vector
	    @param {number} scale
	   
 */
	rat.Vector.prototype.addScaled = function (vec, scale)
	{
		this.x += vec.x * scale;
		this.y += vec.y * scale;
	};

	/** 
	    Subtracts another vector to this one
	    @param {Object} vec rat.Vector
	   
 */
	rat.Vector.prototype.subtract = function (vec)
	{
		this.x -= vec.x;
		this.y -= vec.y;
	};

	/** 
	    Sets a vector from an angle (in radians)
	   	NOTE:  this assumes a default vector of x=1,y=0
	    @param {number} angle
	   
 */
	rat.Vector.prototype.setFromAngle = function (angle)
	{
		var cos = rat.math.cos(angle);
		var sin = rat.math.sin(angle);

		this.x = cos;
		this.y = sin;
	};

	/** 
	    Returns the dot product of two vectors
	    @param {Object} a rat.Vector
	    @param {Object} b rat.Vector
	    @return {number} dotProduct
	   
 */
	rat.Vector.dot = function (a, b)
	{
		return a.x * b.x + a.y * b.y;
	};

	/** 
	    Returns the cross product of two vectors
	    @param {Object} a rat.Vector
	    @param {Object} b rat.Vector
	    @return {number} crossProduct
	   
 */
	rat.Vector.cross = function (a, b)
	{
		// In 2d, the cross product is just a value in Z, so return that value.
		return (a.x * b.y) - (a.y * b.x);
	};

	/** 
	    Build a string that display this vectors values
	    @return {string} vector rat.Vector
	   
 */
	rat.Vector.prototype.toString = function ()
	{
		return "(" + this.x + ", " + this.y + ")";
	};

	/** 
	    Clips vector to fit within a rectangle
	    @param {Object} r rat.shapes.Rect 
	   
 */
	rat.Vector.prototype.limitToRect = function (/*rat.shapes.Rect*/ r)
	{
		if (this.x < r.x)
			this.x = r.x;
		if (this.y < r.y)
			this.y = r.y;
		if (this.x > r.x + r.w)
			this.x = r.x + r.w;
		if (this.y > r.y + r.h)
			this.y = r.y + r.h;
	};

	/** ---------------- rat.Angle ---------------------
	   	I know, we're just tracking a single value here, why is this a class?
	   	1. so we can assign references to it, and multiple objects can have a single changing value to share
	   	2. for ease in performing custom operations on the object, like conversions and math
 */

	/** 
	    @constructor
	    @param {number|Object=} angle rat.Angle
	   
 */
	rat.Angle = function (angle)
	{
		this.angle = angle || 0;
	};

	/** 
	    Returns a copy of this angle
	    @return {Object} angle rat.Angle
	   
 */
	rat.Angle.prototype.copy = function ()
	{
		var newAngle = new rat.Angle();
		newAngle.angle = this.angle;
		return newAngle;
	};

	/** 
	   	set angle from vector.  This means a 0 angle correlates with a vector to the right
	    @param {Object} v rat.Vector
	    @return {Object} angle rat.Vector
	   
 */
	rat.Angle.prototype.setFromVector = function (/*rat.Vector*/ v)
	{
		this.angle = rat.math.atan2(v.y, v.x);
		return this;
	};

	/** 
	   	set an angle facing from source to target
	    @param {Object} source rat.Vector
	    @param {Object} target rat.Vector
	    @return {Object} this rat.Angle
	   
 */
	rat.Angle.prototype.setFromSourceTarget = function (/*rat.Vector*/ source, /*rat.Vector*/ target)
	{
		this.angle = rat.math.atan2(target.y - source.y, target.x - source.x);
		return this;
	};

	/** 
	    Rotate the provided vector, in place by this angle
	    @param {Object} v rat.Vector
	    @return {Object} v rat.Vector
	   
 */
	rat.Angle.prototype.rotateVectorInPlace = function (/*rat.Vector*/ v)
	{
		var cos = rat.math.cos(this.angle);
		var sin = rat.math.sin(this.angle);
		var x = v.x * cos - v.y * sin;
		var y = v.x * sin + v.y * cos;
		v.x = x;
		v.y = y;
		return v;
	};

	/** 
	    Returns a vector rotated by this angle
	    @param {Object} v rat.Vector
	    @param {Object=} dest rat.Vector
	    @return {Object} dest rat.Vector
	   
 */
	rat.Angle.prototype.rotateVector = function (/*rat.Vector*/ v, /*rat.Vector*/dest)
	{
		if (dest)
		{
			dest.x = v.x;
			dest.y = v.y;
		}
		else
			dest = new rat.Vector(v);
		return this.rotateVectorInPlace(dest);
	};

	/** ------ rat.Position --------------
	   	utility:  combine position and angle for a single object, for convenience.
	   	I'm not super comfortable with this name, since it's not clear why a "position" has an angle...
	   	Would love a better name, but don't have one.  What word means "position and orientation"?  Place?  Location?
	   	We do use Place in other places.  At least one example online uses "Position" http:  www.cs.sbu.edu/roboticslab/Simulator/kRobotWebPage/doc/Position.html
	   	Also, maybe should just reimplement all the above, and track x,y,angle instead of breaking them up?
	   	or store them both ways somehow?  could be unnecessary overhead for that.
	   	Another idea - just push all this functionality into Vector, but add 'angle' to vector on the fly...?
 */

	/** 
	    Constructor for position
	    @constructor
	    @param {number} x
	    @param {number} y
	    @param {number|Object} angle rat.Angle 
	   
 */
	rat.Position = function (x, y, angle)
	{
		this.pos = new rat.Vector(x, y);
		this.rot = new rat.Angle(angle);
	};
});
//--------------------------------------------------------------------------------------------------
//
//	A collection of matrix-related classes and functions
//

//------------ rat.Matrix ----------------
rat.modules.add("rat.math.r_matrix",
[
	{ name: "rat.math.r_math", processBefore: true },
],
function (rat)
{
	var math = rat.math;

	//	Avoid creating temporary array objects
	var tm = [[], [], []]; // This only works because we cannot have two executing code paths in this function
	/** 
	    Constructor for Matrix 
	    @constructor
	    @param {?} m Setup with this matrix.  Otherwise, ident 
	   
 */
	rat.Matrix = function (m)	//	constructor for Matrix..  Not defined
	{
		this.m = [[], [], []];
		//	Don't just point at was was passed in
		if (m && m.m)
			this.set(m.m);
		else if (m)
			this.set(m);
		else
			this.loadIdent();
	};

	/** 
	    Set this matrix to identity
	   
 */
	rat.Matrix.prototype.loadIdent = function ()
	{
		//	Just replace the matrix
		this.m[0][0] = 1; this.m[0][1] = 0; this.m[0][2] = 0;
		this.m[1][0] = 0; this.m[1][1] = 1; this.m[1][2] = 0;
		this.m[2][0] = 0; this.m[2][1] = 0; this.m[2][2] = 1;
	};

	/** 
	    transform this matrix
	    @param {number} x
	    @param {number} y
	   
 */
	rat.Matrix.prototype.translateSelf = function (x, y)
	{
		var m1 = this.m;
		m1[0][2] = (m1[0][0] * x) + (m1[0][1] * y) + m1[0][2];
		m1[1][2] = (m1[1][0] * x) + (m1[1][1] * y) + m1[1][2];
		m1[2][2] = (m1[2][0] * x) + (m1[2][1] * y) + m1[2][2];

	};

	/** 
	    rotate this matrix
	    @param {number} r
	   
 */
	rat.Matrix.prototype.rotateSelf = function (r)
	{
		var cos = math.cos(r);
		var sin = math.sin(r);
		var nsin = -sin;
		var m1 = this.m;
		//var m = [[cos, -sin, 0],
		//	     [sin, cos, 0],
		//		 [0, 0, 1]];
		var m00 = (m1[0][0] * cos) + (m1[0][1] * sin);
		var m01 = (m1[0][0] * nsin) + (m1[0][1] * cos);
		//m1[0][2] = m1[0][2];
		var m10 = (m1[1][0] * cos) + (m1[1][1] * sin);
		var m11 = (m1[1][0] * nsin) + (m1[1][1] * cos);
		//m1[1][2] = m1[1][2];
		var m20 = (m1[2][0] * cos) + (m1[2][1] * sin);
		var m21 = (m1[2][0] * nsin) + (m1[2][1] * cos);
		//m1[2][2] = m1[2][2];
		m1[0][0] = m00;
		m1[0][1] = m01;
		m1[1][0] = m10;
		m1[1][1] = m11;
		m1[2][0] = m20;
		m1[2][1] = m21;
	};

	/** 
	    Scale this matrix
	    @param {number} x
	    @param {number} y
	   
 */
	rat.Matrix.prototype.scaleSelf = function (x, y)
	{
		var m1 = this.m;
		m1[0][0] = (m1[0][0] * x);
		m1[0][1] = (m1[0][1] * y);
		//m1[0][2] = m1[0][2];
		m1[1][0] = (m1[1][0] * x);
		m1[1][1] = (m1[1][1] * y);
		//m1[1][2] = m1[1][2];
		m1[2][0] = (m1[2][0] * x);
		m1[2][1] = (m1[2][1] * y);
		//m1[2][2] = m1[2][2];
	};


	/** 
	    Multiply this matrix with another
	    @param {Object} m2 matrix to multiply with
	   
 */
	rat.Matrix.prototype.multSelf = function (m2)
	{
		if (m2.m)
			m2 = m2.m;
		var m1 = this.m;
		tm[0][0] = (m1[0][0] * m2[0][0]) + (m1[0][1] * m2[1][0]) + (m1[0][2] * m2[2][0]);
		tm[0][1] = (m1[0][0] * m2[0][1]) + (m1[0][1] * m2[1][1]) + (m1[0][2] * m2[2][1]);
		tm[0][2] = (m1[0][0] * m2[0][2]) + (m1[0][1] * m2[1][2]) + (m1[0][2] * m2[2][2]);
		tm[1][0] = (m1[1][0] * m2[0][0]) + (m1[1][1] * m2[1][0]) + (m1[1][2] * m2[2][0]);
		tm[1][1] = (m1[1][0] * m2[0][1]) + (m1[1][1] * m2[1][1]) + (m1[1][2] * m2[2][1]);
		tm[1][2] = (m1[1][0] * m2[0][2]) + (m1[1][1] * m2[1][2]) + (m1[1][2] * m2[2][2]);
		tm[2][0] = (m1[2][0] * m2[0][0]) + (m1[2][1] * m2[1][0]) + (m1[2][2] * m2[2][0]);
		tm[2][1] = (m1[2][0] * m2[0][1]) + (m1[2][1] * m2[1][1]) + (m1[2][2] * m2[2][1]);
		tm[2][2] = (m1[2][0] * m2[0][2]) + (m1[2][1] * m2[1][2]) + (m1[2][2] * m2[2][2]);

		// just replace the matrix
		var old = this.m;
		this.m = tm;
		tm = old;
	};

	/** 
	    Get the inverse of matrix (in place)
	    @return {bool} inverted Whether or not the inverse could be taken
	   
 */
	rat.Matrix.prototype.inverseSelf = function ()
	{
		var m = this.m;
		//a1*b2*c3 - a1*b3*c2 - a2*b1*c3 + a2*b3*c1 + a3*b1*c2 - a3*b2*c1
		var d = (m[0][0] * m[1][1] * m[2][2]) - //a1*b2*c3 -
		(m[0][0] * m[1][2] * m[2][1]) - //a1*b3*c2 -
		(m[0][1] * m[1][0] * m[2][2]) + //a2*b1*c3 +
		(m[0][1] * m[1][2] * m[2][0]) + //a2*b3*c1 +
		(m[0][2] * m[1][0] * m[2][1]) - //a3*b1*c2 -
		(m[0][2] * m[1][1] * m[2][0]);  //a3*b2*c1
		if (d === 0)
			return false; // Cannot get the inverse

		//2X2 determinant
		//	a b 
		//	c d
		//	ad - bc

		var inv_d = 1 / d;
		tm[0][0] = inv_d * (m[1][1] * m[2][2] - m[1][2] * m[2][1]);
		tm[0][1] = inv_d * (m[0][2] * m[2][1] - m[0][1] * m[2][2]);
		tm[0][2] = inv_d * (m[0][1] * m[1][2] - m[0][2] * m[1][1]);
		tm[1][0] = inv_d * (m[1][2] * m[2][0] - m[1][0] * m[2][2]);
		tm[1][1] = inv_d * (m[0][0] * m[2][2] - m[0][2] * m[2][0]);
		tm[1][2] = inv_d * (m[0][2] * m[1][0] - m[0][0] * m[1][2]);
		tm[2][0] = inv_d * (m[1][0] * m[2][1] - m[1][1] * m[2][0]);
		tm[2][1] = inv_d * (m[0][1] * m[2][0] - m[0][0] * m[2][1]);
		tm[2][2] = inv_d * (m[0][0] * m[1][1] - m[0][1] * m[1][0]);

		var old = this.m;
		this.m = tm;
		tm = old;
		return true;
	};

	/** 
	    Set this matrix with an array of arrays
	    @param {?} m become this matrix
	   
 */
	rat.Matrix.prototype.set = function (m)
	{
		if (m.m)
			m = m.m;
		//	Manual copy to avoid pointing to the matrix passed
		var self = this.m;
		self[0][0] = m[0][0];
		self[0][1] = m[0][1];
		self[0][2] = m[0][2];
		self[1][0] = m[1][0];
		self[1][1] = m[1][1];
		self[1][2] = m[1][2];
		self[2][0] = m[2][0];
		self[2][1] = m[2][1];
		self[2][2] = m[2][2];
	};

	/** 
	    Transform a point in place by this matrix 
	    @param {Object=} p point object
	   
 */
	rat.Matrix.prototype.transformPointSelf = function (p)
	{
		var m = this.m;
		var tx = p.x;
		var ty = p.y;
		p.x = (m[0][0] * tx) + (m[0][1] * ty + m[0][2]);
		p.y = (m[1][0] * tx) + (m[1][1] * ty + m[1][2]);
		return p;
	};

	/** 
	    Transform a point by this matrix 
	    @param {Object=} p point object
	    @param {Object=} dest point object
	    @return {Object} point The transformed point
	   
 */
	rat.Matrix.prototype.transformPoint = function (p, dest)
	{
		if (!dest)
			dest = new rat.Vector(p);
		else
		{
			dest.x = p.x;
			dest.y = p.y;
		}
		return this.transformPointSelf(dest);
	};

	/** 
	   	Static method to allow matrix multiplication
	    @param {Object} m1 The first matrix
	    @param {Object} m2 The second matrix
	    @param {Object=} dest destination matrix
	    @return {Object} dest the multiplied matrix
	   
 */
	rat.Matrix.matMult = function (m1, m2, dest)
	{
		if (dest)
			dest.set(m1);
		else
			dest = new rat.Matrix(m1);
		dest.multSelf(m2);
		return dest;
	};

	/** 
	    Get the inverse of one matrix into another matrix
	    @param {dest=} The destination of the matrix inverse
	    @return {dest} The matrix inverse
	   
 */
	rat.Matrix.prototype.inverse = function (dest)
	{
		if (!dest)
			dest = new rat.Matrix(this);
		else
			dest.set(this);
		dest.inverseSelf();
		return dest;
	};
});
//--------------------------------------------------------------------------------------------------
//
// Graphics objects and utilities
//
rat.modules.add("rat.graphics.r_graphics",
[
	{ name: "rat.math.r_math", processBefore: true },

	"rat.graphics.r_color",
	"rat.os.r_system",
	"rat.os.r_events",
	"rat.debug.r_console",
	"rat.debug.r_profiler",
	"rat.math.r_matrix",
	"rat.utils.r_utils",
	"rat.utils.r_shapes",
],
function (rat)
{
	var math = rat.math; // for quick local access below

	/**  Update our held view state.
 */
	function getViewState()
	{
		if (rat.system.has.winJS)
		{
			//	see https://msdn.microsoft.com/en-US/library/windows/apps/windows.ui.viewmanagement.applicationviewstate
			var states = window.Windows.UI.ViewManagement.ApplicationViewState;
			switch (rat.graphics.winJSAppView.value)
			{
				case states.fullScreenLandscape:
					rat.graphics.viewState = "fullscreen";
					break;
				case states.filled:
					rat.graphics.viewState = "filled";
					break;
				case states.snapped:
					rat.graphics.viewState = "snapped";
					break;
				case states.fullscreenPortrait:
					rat.graphics.viewState = "fullscreenPortrait";
					break;
				default:
					rat.graphics.viewState = "unknown";
					break;
			}
			rat.graphics.viewState = rat.graphics.winJSAppView.value;	//	for convenience later
		}
		else
			rat.graphics.viewState = "fullscreen";
	}

	// remember old known size, so we know whether to bother informing anyone.
	var oldSize = { x: 0, y: 0 };
	/**  our screen size changed.  Tell people about it.
 */
	function screenSizeChanged()
	{
		var newSize = { x: rat.graphics.SCREEN_WIDTH, y: rat.graphics.SCREEN_HEIGHT };

		if (oldSize.x !== newSize.x || oldSize.y !== newSize.y)
		{
			rat.events.fire("resize", newSize, oldSize);
			oldSize.x = newSize.x;
			oldSize.y = newSize.y;
		}
	}

	/** 
	   	rat graphics module
	    @namespace
	   
 */
	rat.graphics = {

		SCREEN_WIDTH: 760, //	gets reset correctly later
		SCREEN_HEIGHT: 600, //	gets reset correctly later

		// auto scaling to match desired display as closely as we can
		globalScale: { x: void 0, y: void 0 },	// current scale, if there is one
		globalTranslate: null,	// current shift, if there is one
		autoTargetSize: { x: 0, y: 0 },	// ideal size
		autoResizeCanvas: true,	// turn on/off auto resizing system
		autoFillOnResize: false, // once we resize close enough, go ahead and accept the window size (see below)
		autoCenterCanvas: false, // turn on/off style-based canvas centering inside its parent

		// factor in high-DPI devices when sizing
		// A reason this is off by default is that on high-DPI devices we'll do a lot more rendering work, slowing us down.
		// If performance is a concern, don't turn this on!
		autoScaleWithDevicePixelRatio: false,
		canvasPixelRatio: 1,

		autoClearCanvas: false,	// whether or not we should clear canvas automatically each frame
		autoClearColor: "#000",	// color to which we should autoclear, or "" if we should clear to transparent.

		frameIndex: 0,		// current frame since launch of application
		ctx: null,

		frameStats: { totalElementsDrawn: 0 },	// for debug, some stats about what we've drawn this frame

		cursorPos: { x: -1, y: -1 },	// last known cursor pos, in canvas space

		minAutoScale: 0.0000001, // Minimum auto scale defaults to 0.00000001

		mTransform: void 0,	// our internally tracked transformation matrix
		mStateStack: [],

		viewState: "fullscreen"
	};

	var mIgnoreRatTransform = false; // Should matrix calculations ignore the rat matrix.  Set via rat.graphics.save

	//	not needed by default - we grab it above.
	//	Use this if you want to override or whatever...?
	//	or above function may change, since it's kinda hardcoded.
	rat.graphics.setContext = function (ctx)
	{
		rat.graphics.ctx = ctx;
	};

	rat.graphics.getContext = function ()
	{
		return rat.graphics.ctx;
	};



	/**  
	    Init rat.graphics
	    @param {string} canvasID optional
	    @suppress {missingProperties | checkTypes}
	    Again, suppress in favor of a registered init func
	    
 */
	rat.graphics.init = function (canvasID, ops)
	{
		ops = ops || {};
		mIgnoreRatTransform = !!ops.ignoreRatTransform;
		var rGraphics = rat.graphics;
		
		canvasID = canvasID || "canvas";	//	optional, otherwise assume canvas called "canvas"
		//	todo: support canvas object argument?
		//	todo: support no existing canvas, in which case create one and add to document

		// set the transform up
		rGraphics.mTransform = new rat.Matrix();
		
		rGraphics.canvas = document.getElementById(canvasID);
		if (rat.system.has.Wraith && Wraith.w_onPlatform === "PS4")
		{
			rGraphics.canvas.style = {};
		}
		rGraphics.ctx = rGraphics.canvas.getContext("2d");
		rGraphics.SCREEN_WIDTH = rGraphics.canvas.width;
		rGraphics.SCREEN_HEIGHT = rGraphics.canvas.height;

		rGraphics.winJSAppView = void 0;
		if (rat.system.has.winJS)
			rGraphics.winJSAppView = window.Windows.UI.ViewManagement.ApplicationView;

		// set up a normal window resize callback
		getViewState();
		rat.addOSEventListener(window, 'resize', rGraphics.resizeCallback);
		screenSizeChanged();
	};

	// This is our callback for screen resize events from the browser/os... this is where we first learn about resizes.
	rat.graphics.resizeCallback = function (eventArgs)
	{
		getViewState();

		rat.events.fire("before_resize", eventArgs);
		//console.log("resizeCallback " + rat.graphics.callAutoScale);
		if (rat.graphics.callAutoScale)
			rat.graphics.autoScaleCallback();
	};

	rat.graphics.setAutoClear = function (doAutoClear, autoClearStyle)
	{
		rat.graphics.autoClearCanvas = doAutoClear;
		if (typeof (autoClearStyle !== 'undefined'))
			rat.graphics.autoClearColor = autoClearStyle;
	};

	//	clear canvas to autoclear color
	rat.graphics.clearCanvas = function ()
	{
		var ctx = rat.graphics.getContext();
		if (rat.graphics.autoClearColor === "" || (rat.graphics.Video && rat.graphics.Video.numberOfActiveBGVideos > 0))
			ctx.clearRect(0, 0, rat.graphics.SCREEN_WIDTH, rat.graphics.SCREEN_HEIGHT);
		else
		{
			ctx.fillStyle = rat.graphics.autoClearColor;
			ctx.fillRect(0, 0, rat.graphics.SCREEN_WIDTH, rat.graphics.SCREEN_HEIGHT);
		}
	};

	//	begin a new frame (mostly just for counting and tracking purposes, currently)
	rat.graphics.beginFrame = function ()
	{
		rat.graphics.frameIndex++;
		rat.graphics.frameStats.totalElementsDrawn = 0;

		if (rat.profiler && rat.profiler.beginFrame)
			rat.profiler.beginFrame();
	};

	//	end the current frame
	rat.graphics.endFrame = function ()
	{
		if (rat.profiler && rat.profiler.endFrame)
			rat.profiler.endFrame();
	};

	//
	//	Set global scale for shrinking or expanding rendering
	//	e.g. to fit our larger screen in a smaller space, like my sad laptop.
	//
	rat.graphics.setGlobalScale = function (x, y)
	{
		x = x || 1;
		y = y || 1;
		// Ugh...  this is getting complicated.
		// the problem now is that sometimes the scale isn't changing, e.g. if rat.graphics.autoFillOnResize,
		// but SCREEN_WIDTH and SCREEN_HEIGHT are not being fixed...
		// So, why was this check here, anyway?  If it's needed, better work out some other solution for autoFillOnResize, or check that flag here?
		//if (x !== rat.graphics.globalScale.x ||
		// y !== rat.graphics.globalScale.y )
		{
			rat.graphics.globalScale.x = x;
			rat.graphics.globalScale.y = y;
			//rat.console.log("RAT: Global scale set to " + JSON.stringify(rat.graphics.globalScale));

			//	the idea is to pretend like we have a bigger space than we do.
			//	so, if we're scaling down, scale internal effective screen size variables up.
			rat.graphics.SCREEN_WIDTH = (rat.graphics.canvas.width / rat.graphics.globalScale.x) | 0;
			rat.graphics.SCREEN_HEIGHT = (rat.graphics.canvas.height / rat.graphics.globalScale.y) | 0;

			screenSizeChanged();
		}
	};
	rat.graphics.clearGlobalScale = function ()
	{
		rat.graphics.globalScale.x = void 0;
		rat.graphics.globalScale.y = void 0;
		rat.graphics.SCREEN_WIDTH = rat.graphics.canvas.width;
		rat.graphics.SCREEN_HEIGHT = rat.graphics.canvas.height;
		screenSizeChanged();
	};

	rat.graphics.setGlobalTranslate = function (x, y)
	{
		rat.graphics.globalTranslate = { x: x, y: y };
	};
	rat.graphics.clearGlobalTranslate = function ()
	{
		rat.graphics.globalTranslate = { x: 0, y: 0 };
	};

	//	return true if we're currently applying a global scale
	rat.graphics.hasGlobalScale = function ()
	{
		var gblScale = rat.graphics.globalScale;
		var hasGlobalScale = gblScale.x && gblScale.y && (gblScale.x !== 1.0 || gblScale.y !== 1.0);
		return hasGlobalScale;
	};

	//	and if you want to temporarily ignore global scale and translation (e.g. to match real screen coordinates)...
	//	Use these like this:
	//	rat.graphics.save();
	//	rat.graphics.counterGlobalScale(ctx);
	//	rat.graphics.counterGlobalTranslate(ctx);
	//	... my drawing ...
	//	rat.graphics.restore();
	rat.graphics.counterGlobalScale = function (tctx)
	{
		if (rat.graphics.hasGlobalScale())
			rat.graphics.scale(1 / rat.graphics.globalScale.x, 1 / rat.graphics.globalScale.y, tctx);
	};
	rat.graphics.counterGlobalTranslate = function (tctx)
	{
		if (rat.graphics.globalTranslate)
			rat.graphics.translate(-rat.graphics.globalTranslate.x, -rat.graphics.globalTranslate.y, tctx);
	};

	//	autoscale to an ideal target window size.
	//	this is only used if enabled, and only when window is resized
	rat.graphics.autoScaleCallback = function ()
	{
		var winSize = rat.utils.getWindowSize();
		var scale = winSize.w / rat.graphics.autoTargetSize.w;	//	in order to fit horiz
		var altScale = winSize.h / rat.graphics.autoTargetSize.h;	//	in order to fit vert
		if (altScale < scale)	//	use whichever is smaller
			scale = altScale;

		//	support minimum scale down.
		//	This is useful for things like not scaling down to super-mini-size in snap state.
		if (scale < rat.graphics.minAutoScale)
			scale = rat.graphics.minAutoScale;

		//	Cap scale.  Could change to allow this by default?
		//	It's not a big problem, just not what I want right now.  Anyway, changeable with flag.
		if (!rat.graphics.allowAutoUpscale && scale > 1)
			scale = 1;

		if (rat.graphics.autoResizeCanvas && rat.graphics.canvas)
		{
			var width = math.floor(rat.graphics.autoTargetSize.w * scale);
			var height = math.floor(rat.graphics.autoTargetSize.h * scale);
			var remainingWidth = winSize.w - width;
			var remainingHeight = winSize.h - height;

			//	OK, but for systems like win8, we don't want ugly black bars on the sides...
			if (rat.graphics.autoFillOnResize)
			{
				//	We got as close as we could for game logic to match its internal scale.
				//	Now just fill in any leftover space in canvas, with this new scale.
				//	Note that this leaves rat.graphics.SCREEN_HEIGHT and SCREEN_WIDTH at new values,
				//	(and aspect ratio will have changed)
				//	which the game needs to expect, and do its own centering to deal with.
				//	If you want a simpler solution, consider autoCenterCanvas below
				width = winSize.w;
				height = winSize.h;
			}

			//	check for some incompatible flags...
			if (rat.graphics.autoScaleWithDevicePixelRatio && !rat.graphics.allowAutoUpscale)
			{
				//	scaling to device pixel ratio requires an upscale, usually.
				//	though...  hmm...
				//	TODO:  Get rid of this warning and check below.
				//		Instead, check !allowAutoUpscale below and specifically check if scale is > 1
				//		and if so, limit our scale back down again.
				rat.console.log("WARNING: autoScaleWithDevicePixelRatio is not compatible with rat.graphics.allowAutoUpscale being false.");
			}

			//	on top of all that, factor in device pixel ratio, if requested.
			//	This is to adapt to high-DPI devices,
			//	like a new iPad, or the Helix, or the 2in1 Intel laptop
			//	This is relatively new code, and hasn't been tested on various browsers, host environments,
			//	etc., and I don't know how well it will interact with other flags like centering, max scaling, filling, etc.
			//
			//	It makes things look really nice in Chrome on high-end devices,
			//	at some performance cost.
			//	Also works in IE 10+, with a slightly more noticeable performance cost with lots of text.
			//	Works in Win8 now (see custom code below), but there we have weird clip problems, so not using it in any apps yet.
			//
			if (rat.graphics.autoScaleWithDevicePixelRatio && rat.graphics.allowAutoUpscale)
			{
				var ctx = rat.graphics.canvas.getContext('2d');

				//	BROWSER version
				var devicePixelRatio = window.devicePixelRatio || 1;

				var backingStoreRatio = ctx.webkitBackingStorePixelRatio ||
							ctx.mozBackingStorePixelRatio ||
							ctx.msBackingStorePixelRatio ||
							ctx.oBackingStorePixelRatio ||
							ctx.backingStorePixelRatio || 1;

				//	WINDOWS 8+ implementation
				if (rat.system.has.windows8 && Windows && Windows.Graphics && Windows.Graphics.Display)
				{
					//	see https://github.com/yoik/cordova-yoik-screenorientation/issues/35 for correctly dealing with this in Win 8 and Win8.1,
					//	since things changed!
					var displayInformation = (Windows.Graphics.Display.DisplayInformation) ? Windows.Graphics.Display.DisplayInformation.getForCurrentView() : Windows.Graphics.Display.DisplayProperties;

					if (displayInformation)
					{
						//var logicalDPI = displayInformation.logicalDpi;	//	interesting...
						backingStoreRatio = 100;
						devicePixelRatio = displayInformation.resolutionScale;
					}
				}

				var ratio = devicePixelRatio / backingStoreRatio;
				rat.graphics.canvasPixelRatio = ratio;
				//	todo: support capping this value for performance reasons?

				//	tell style to scale us down to fit in the same browser space
				rat.graphics.canvas.style.width = "" + width + "px";
				rat.graphics.canvas.style.height = "" + height + "px";
				//	but render big!
				width *= ratio;
				height *= ratio;
				scale *= ratio;

				if (ratio !== 1)
				{
					rat.console.log("device pixel ratio " + ratio + " : " + devicePixelRatio + " / " + backingStoreRatio);
				}
			}

			rat.graphics.canvas.width = width;
			rat.graphics.canvas.height = height;

			//	alternatively, accept the black bars, but make sure everything is centered.
			//	(in contrast with autoFillOnResize, which is generally preferred for a few reasons)
			//	This is a new attempt, useful for quicker development.
			//	Makes certain assumptions about html setup.
			if (rat.graphics.autoCenterCanvas)
			{
				var dw = math.floor(remainingWidth / 2);
				var dh = math.floor(remainingHeight / 2);
				rat.graphics.canvas.style["margin-left"] = "" + dw + "px";
				rat.graphics.canvas.style["margin-top"] = "" + dh + "px";
			}

		}

		//	STT moved this here from above 2013.6.6
		//	This is correct, I think, so rat.graphics.SCREEN_xxx is set correctly
		rat.graphics.setGlobalScale(scale, scale);

		//rat.console.log("autoScaleCallback: " + scale + " canvas: " + rat.graphics.canvas.width + "," + rat.graphics.canvas.height);
	};

	//	set up automated scaling when user resizes window
	//	This mostly just sets a bunch of globals for how to deal with resizes,
	//	and triggers one call now to force setup.
	rat.graphics.setAutoScaleFromIdeal = function (targetW, targetH, scaleOptions, oldArg2)
		//used to take two explicit flags: resizeCanvas, allowAutoUpscale
	{
		
		//	no options?
		if (typeof(scaleOptions) === "undefined")
		{
			scaleOptions = {
				autoResizeCanvas : true,
				allowAutoUpscale : true,
				autoFillOnResize : false,
			};
		//	handle old-style flags passed in directly...		
		} else if (typeof(scaleOptions) !== "object")
		{
			var newOptions = {
				autoResizeCanvas : !!scaleOptions,	//	was optional autoResizeCanvas flag
				allowAutoUpscale : !!oldArg2,
			};
			scaleOptions = newOptions;
		}
		
		//	transfer all that to various global flags, leaving previous values if not specified
		
		rat.graphics.autoTargetSize.w = targetW;
		rat.graphics.autoTargetSize.h = targetH;
		
		if (scaleOptions.autoResizeCanvas !== void 0)
			rat.graphics.autoResizeCanvas = scaleOptions.autoResizeCanvas;
		if (scaleOptions.allowAutoUpscale !== void 0)
			rat.graphics.allowAutoUpscale = scaleOptions.allowAutoUpscale;
		if (scaleOptions.autoFillOnResize !== void 0)
			rat.graphics.autoFillOnResize = scaleOptions.autoFillOnResize;
		
		rat.graphics.callAutoScale = true;	//	remember to call autoscale function later on resize event

		//	also call once now just to get things right to start with
		rat.graphics.autoScaleCallback();
	};

	//	This is a set of routines to modify the transformation of the given context.
	//	These will apply the change, but also track it internally so that we can do some extra stuff,
	//	like ask for the current transformation at any given time, do our own transform math, etc.
	//	the ctx argument here is last, so that it can be optional, in which case we use the current rat ctx
	/**
	 * Rotate the current matrix
	 * @param {number} r
	 * @param {Object=} tctx
	 */
	rat.graphics.rotate = function (r, tctx)
	{
		var ctx = tctx || rat.graphics.ctx;
		if (!mIgnoreRatTransform)
			rat.graphics.mTransform.rotateSelf(r);
		ctx.rotate(r);
	};

	/**
	 * Scale the current matrix
	 * @param {number} x
	 * @param {number} y
	 * @param {Object=} tctx
	 */
	rat.graphics.scale = function (x, y, tctx)
	{
		if (y === void 0)
			y = x;
		var ctx = tctx || rat.graphics.ctx;
		if (!mIgnoreRatTransform)
			rat.graphics.mTransform.scaleSelf(x, y);
		ctx.scale(x, y);
	};

	/**
	 * Translate the current matrix
	 * @param {number} x
	 * @param {number} y
	 * @param {Object=} tctx
	 */
	rat.graphics.translate = function (x, y, tctx)
	{
		var ctx = tctx || rat.graphics.ctx;
		if (!mIgnoreRatTransform)
			rat.graphics.mTransform.translateSelf(x, y);
		ctx.translate(x, y);
	};

	rat.graphics.transform = function (m11, m12, m21, m22, dx, dy, tctx)
	{
		if (Array.isArray(m11.m))
			m11 = m11.m;
		if (Array.isArray(m11))
		{
			//	m11, m12, m21, m22, dx, dy, tctx
			//	m11, dx, dy, tctx
			tctx = m22;
			m12 = m11[1][0];
			m21 = m11[0][1];
			m22 = m11[1][1];
			dx = m11[0][2];
			dy = m11[1][2];
			m11 = m11[0][0];
		}
		var ctx = tctx || rat.graphics.ctx;
		if (!mIgnoreRatTransform)
		{
			var m = [[m11, m12, dx], [m21, m22, dy], [0, 0, 1]];
			rat.graphics.mTransform.multSelf(m);// = rat.Matrix.matMult(rat.graphics.mTransform, m);
		}
		ctx.transform(m11, m12, m21, m22, dx, dy);
	};

	rat.graphics.setTransform = function (m11, m12, m21, m22, dx, dy, tctx)
	{
		rat.graphics.resetTransform();
		rat.graphics.transform(m11, m12, m21, m22, dx, dy, tctx);
	};

	/**
	 * Reset the transformation matrix
	 * @param {Object=} tctx
	 */
	rat.graphics.resetTransform = function (tctx)
	{
		var ctx = tctx || rat.graphics.ctx;
		if (!mIgnoreRatTransform)
			rat.graphics.mTransform.loadIdent();
		ctx.setTransform(1, 0, 0, 1, 0, 0);
	};

	/** 
	 * Save the current rendering state
	 * @param {Object=} options  {ignoreRatMat:true}  You can never set ignoreRatMat to false if it was true.  it goes back to false after a .restore
	 */
	rat.graphics.save = function (options)
	{
		var ctx = rat.graphics.ctx;
		options = options || {};
		if (options.ignoreRatMat === void 0)
			options.ignoreRatMat = mIgnoreRatTransform;
		else
		{
			//once ignoreRatMat is set to true, it only goes to false with a restore.  You cannot set it to false with a save!
			options.ignoreRatMat = mIgnoreRatTransform || options.ignoreRatMat || false;
		}
		ctx.save();
		var state = {
			ignoreRatMat: mIgnoreRatTransform,
			transform: void 0
		};
		mIgnoreRatTransform = options.ignoreRatMat;

		//	Get a new version of the .m so that we are not pointing to the same thing
		// We only need to do this if we care about the rat matrix.
		if (!mIgnoreRatTransform)
		{
			state.transform = rat.graphics.mTransform.m;
			rat.graphics.mStateStack.push(state);
			// DON'T POINT at the old matrix.
			var m = rat.graphics.mTransform.m;
			// THIS IS A COPY!
			m = [[m[0][0], m[0][1], m[0][2]],
			 [m[1][0], m[1][1], m[1][2]],
			 [m[2][0], m[2][1], m[2][2]]];
			rat.graphics.mTransform.m = m;
		}
		else
		{
			rat.graphics.mStateStack.push(state);// DON'T POINT at the old matrix.
		}
	};

	/**  Restore a saved rendering state
 */
	rat.graphics.restore = function ()
	{
		var ctx = rat.graphics.ctx;
		ctx.restore();
		var state = rat.graphics.mStateStack.pop();
		mIgnoreRatTransform = state.ignoreRatMat;
		if (state.transform)
			rat.graphics.mTransform.m = state.transform;
	};

	rat.graphics.getTransform = function ()
	{
		return rat.graphics.mTransform;
	};

	/** @param {Object=} dest */
	rat.graphics.transformPoint = function (p, dest)
	{
		return rat.graphics.mTransform.transformPoint(p, dest);
	};

	/** Push a gfx profiling mark.   Only works under wraith */
	rat.graphics.pushPerfMark = function (label, ctx)
	{
		if (!ctx)
			ctx = rat.graphics.ctx;
		if (ctx.pushPerfMark)
			ctx.pushPerfMark(label);
	};

	/** Pop off a gfx profiling mark.   Only works under wraith */
	rat.graphics.popPerfMark = function (ctx)
	{
		if (!ctx)
			ctx = rat.graphics.ctx;
		if (ctx.popPerfMark)
			ctx.popPerfMark();

	};
	
	/** set canvas imagesmoothing flag on/off
		return previously set smoothing flag
	*/
	rat.graphics.setImageSmoothing = function (smoothingOn, ctx)
	{
		var oldSmoothing = false;
		
		if (!ctx)
			ctx = rat.graphics.ctx;
		
		//	this is a problem area in html5 games.
		//	This will work on some browsers on some versions.
		//	see:
		//		https://software.intel.com/en-us/html5/hub/blogs/state-of-nearest-neighbor-interpolation-in-canvas/
		//		http://stackoverflow.com/questions/7615009/disable-interpolation-when-scaling-a-canvas
		//		http://phoboslab.org/log/2012/09/drawing-pixels-is-hard
		//		https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingEnabled
		//		
		//	We may want to try harder in the future in other ways.
		//	This is OK for now in recent chrome, which I mostly use.
		
		if (ctx.imageSmoothingEnabled !== void 0)	//	standard name supported? just set that, and avoid warnings
		{
			oldSmoothing = ctx.imageSmoothingEnabled;
			ctx.imageSmoothingEnabled = smoothingOn;
		}
		else {
			
			if (ctx.mozImageSmoothingEnabled !== void 0)
				oldSmoothing = ctx.mozImageSmoothingEnabled;
			else if (ctx.webkitImageSmoothingEnabled !== void 0)
				oldSmoothing = ctx.webkitImageSmoothingEnabled;
			else if (ctx.msImageSmoothingEnabled !== void 0)
				oldSmoothing = ctx.msImageSmoothingEnabled;
			
			//	otherwise, use older names
			ctx.mozImageSmoothingEnabled = smoothingOn;
			ctx.webkitImageSmoothingEnabled = smoothingOn;
			ctx.msImageSmoothingEnabled = smoothingOn;
		}
		
		//	return old value before we changed it
		return oldSmoothing;
	};
	
	/**
	 * Draw a line
	 * @param {Object|Number} p1
	 * @param {Object|Number} p2
	 * @param {Object|Number=} p3
	 * @param {Number=} p4
	 */
	rat.graphics.drawLine = function (p1, p2, p3, p4, ops)
	{
		var point1 = { x: 0, y: 0 };
		var point2 = { x: 0, y: 0 };
		if (p1.x !== void 0)
		{
			point1 = p1;
			if (p2.x !== void 0)
			{
				point2 = p2;
				ops = p3;
			}
			else
			{
				point2.x = p2;
				point2.y = p3;
				ops = p4;
			}
		}
		else
		{
			point1.x = p1;
			point1.y = p2;
			if (p3.x !== void 0)
			{
				point2 = p3;
				ops = p4;
			}
			else
			{
				point2.x = p3;
				point2.y = p4;
			}
		}
		var ctx = rat.graphics.ctx;

		if (ops)
		{
			if (ops.color)
				ctx.strokeStyle = ops.color.toString();
			if (ops.lineWidth !== void 0)
				ctx.lineWidth = ops.lineWidth;
		}

		ctx.beginPath();
		ctx.moveTo(point1.x, point1.y);
		ctx.lineTo(point2.x, point2.y);
		ctx.stroke();
	};

	/**
	 * Draw some text
	 * @param {string} text
	 * @param {number=} x
	 * @param {number=} y
	 * @param {number=} maxWidth
	 */
	rat.graphics.drawText = function (text, x, y, maxWidth)
	{
		x = x || 0;
		y = y || 0;
		if (maxWidth)
			rat.graphics.ctx.fillText(text, x, y, maxWidth);
		else
			rat.graphics.ctx.fillText(text, x, y);
	};

	/**
	 * Draw a text in an arc
	 */
	/** 	 See http:  www.html5canvastutorials.com/labs/html5-canvas-text-along-arc-path/
 */
	rat.graphics.drawTextArc = function (str, centerX, centerY, radius, angle, options)
	{
		options = options || {};
		var len;
		if (!angle)
		{
			len = rat.graphics.ctx.measureText(str);
			if (len.width !== void 0)
				len = len.width;
			angle = rat.math.min(len / radius);
			if (options.angleScale)
				angle *= options.angleScale;
		}
		var context = rat.graphics.ctx;

		if (options.stroke)
		{
			if (options.lineWidth)
				context.lineWidth = options.lineWidth;
		}

		var s;
		len = str.length;
		context.save();
		context.translate(centerX, centerY);
		context.rotate(-1 * angle / 2);
		context.rotate(-1 * (angle / len) / 2);
		for (var n = 0; n < len; n++)
		{
			context.rotate(angle / len);
			context.save();
			context.translate(0, -1 * radius);
			s = str[n];
			if (options.stroke)
				context.strokeText(s, 0, 0);
			else
				context.fillText(s, 0, 0);
			context.restore();
		}
		context.restore();
	};

	/**
	 * Draw an open (not filled) circle (rat.shapes.Circle)
	 * @param {Object|number} circ
	 * @param {number=} y
	 * @param {number=} r
	 */
	rat.graphics.drawCircle = function (circ, y, r, ops)
	{
		var ctx = rat.graphics.ctx;
		ctx.beginPath();
		if (circ.center !== void 0)
		{
			ops = y;
			r = circ.radius;
			y = circ.center.y;
			circ = circ.center.x;
		}
		else if (circ.x !== void 0)
		{
			if (circ.r !== void 0)
			{
				ops = y;
				r = circ.r;
				y = circ.y;
				circ = circ.x;
			}
			else
			{
				ops = r;
				r = y;
				y = circ.y;
				circ = circ.x;
			}
		}

		if (ops !== void 0)
		{
			if (ops.color)
			{
				if (ops.fill)
					ctx.fillStyle = ops.color.toString();
				else
					ctx.strokeStyle = ops.color.toString();
			}
			if (!ops.fill && ops.lineWidth !== void 0)
				ctx.lineWidth = ops.lineWidth;
		}
		ctx.arc(circ, y, r, 0, rat.math.PI2, true);
		ctx.closePath();

		if (ops && ops.fill)
			ctx.fill();
		else
			ctx.stroke();
	};

	/**
	 * Draw a rectangle (rat.shapes.Rect)
	 * @param {Object} rect
	 * @param {Object=} ops
	 */
	rat.graphics.drawRect = function (rect, ops)
	{
		ops = ops || {};
		if (ops.fill)
		{
			if (ops.color)
				rat.graphics.ctx.fillStyle = ops.color.toString();
			rat.graphics.ctx.fillRect(rect.x, rect.y, rect.w, rect.h);
		}
		else
		{
			if (ops.lineWidth)
				rat.graphics.ctx.lineWidth = ops.lineWidth;
			if (ops.color)
				rat.graphics.ctx.stokeStyle = ops.color.toString();
			rat.graphics.ctx.strokeRect(rect.x, rect.y, rect.w, rect.h);
		}
	};

	/**
	 * Draw a list of shapes (see r_collision2d)
	 * @param {Array} list
	 */
	rat.graphics.drawShapeList = function (list)
	{
		if (!list || list.length <= 0)
			return;
		var shape;
		for (var index = 0; index !== list.length; ++index)
		{
			shape = list[index];
			switch (shape.type)
			{
				case 'circle':
					rat.graphics.drawCircle(shape);
					break;
				case 'rect':
					rat.graphics.drawRect(shape);
					break;
				default:
					rat.console.log("Unable able to identify shape for drawing\n");
					break;
			}

			//	Draw the children
			rat.graphics.drawShapeList(shape.children);
		}
	};

	//
	//--------- more graphics utils ----------------
	//

	//	@todo: move to graphics utils (separate module)?
	//	maybe r_draw, and call them rat.draw_whatever...?
	//	also, switch to a parameter object instead of this long list of args?
	//	color values here can be strings, or anything with a toString method.
	rat.graphics.drawFillbar = function (x, y, w, h, fillCur, fillTotal, backColor, bodyColor, frameColor, frameWidth)
	{
		var fillPoint = fillCur * w / fillTotal;
		var ctx = rat.graphics.getContext();
		ctx.fillStyle = backColor.toString();
		ctx.fillRect(x, y, w, h);

		ctx.fillStyle = bodyColor.toString();
		ctx.fillRect(x, y, fillPoint, h);

		ctx.lineWidth = frameWidth || 1;
		ctx.strokeStyle = frameColor.toString();
		ctx.strokeRect(x, y, w, h);
	};
	rat.graphics.draw_fillbar = rat.graphics.drawFillbar;	//	old inconsistent name

	// Don't make or use global functions.
	//draw_fillbar = rat.graphics.draw_fillbar;	//	shorter name for backwards compat

	//	util to make a unilateral polygon (e.g. triangle) shape.
	//	rotate of 0 results in a point on the right hand side
	//	You probably want to call fillPolygon or strokePolygon
	rat.graphics.makePolygonShape = function (centerX, centerY, radius, rotate, sides)
	{
		if (typeof (sides) === 'undefined')
			sides = 3;
		if (typeof (rotate) === 'undefined')
			rotate = 0;
		var rotInc = Math.PI * 2 / sides;

		rat.graphics.ctx.beginPath();
		for (var i = 0; i < sides; i++)
		{
			var x = Math.cos(rotate + i * rotInc) * radius;
			var y = Math.sin(rotate + i * rotInc) * radius;
			if (i === 0)
				rat.graphics.ctx.moveTo(centerX + x, centerY + y);
			else
				rat.graphics.ctx.lineTo(centerX + x, centerY + y);
		}
		rat.graphics.ctx.closePath();
	};

	rat.graphics.fillPolygon = function (centerX, centerY, radius, rotate, sides)
	{
		rat.graphics.makePolygonShape(centerX, centerY, radius, rotate, sides);
		rat.graphics.ctx.fill();
	};

	rat.graphics.strokePolygon = function (centerX, centerY, radius, rotate, sides)
	{
		rat.graphics.makePolygonShape(centerX, centerY, radius, rotate, sides);
		rat.graphics.ctx.stroke();
	};
	
	rat.graphics.radialClip = function(ctx, cx, cy, radius, startAngle, endAngle, counterClockwise)
	{
		ctx.beginPath();
		ctx.moveTo( cx, cy );

		ctx.arc( cx, cy, radius, startAngle, endAngle, counterClockwise );
		ctx.closePath();
		ctx.clip();
	};

	//	define a round rect path in the rat context
	//	(to be filled/stroked/whatever externally)
	//	arguments:  rect that defines bounds, corner radius
	rat.graphics.roundRect = function (rect, cornerRadius)
	{
		var pi = Math.PI;
		var ctx = rat.graphics.ctx;
		ctx.beginPath();
		
		//	Start with the top left arc
		var x = rect.x + cornerRadius;
		var y = rect.y + cornerRadius;
		ctx.arc(x, y, cornerRadius, pi, pi * 1.5);
		
		//	across top
		var x = rect.x + rect.w - cornerRadius;
		ctx.lineTo(x, rect.y);
		//	right top corner
		ctx.arc(x, y, cornerRadius, pi * 1.5, pi * 2);
		
		var y = rect.y + rect.h - cornerRadius;
		ctx.lineTo(rect.x + rect.w, y);
		//	bottom right corner
		ctx.arc(x, y, cornerRadius, 0, pi * 0.5);
		
		var x = rect.x + cornerRadius;
		ctx.lineTo(x, rect.y + rect.h);
		//	bottom left corner
		ctx.arc(x, y, cornerRadius, pi * 0.5, pi);
		
		//	this will finish up the last line
		ctx.closePath();
	};

	rat.graphics.drawMiniArrow = function (x, y, color)
	{
		var ctx = rat.graphics.ctx;

		ctx.strokeStyle = color;
		ctx.lineWidth = 3;
		ctx.beginPath();
		ctx.moveTo(x - 2, y);
		ctx.lineTo(x + 7, y);
		ctx.closePath();
		ctx.stroke();
		ctx.beginPath();

		ctx.lineWidth = 2;
		ctx.moveTo(x + 4, y - 3);
		ctx.lineTo(x + 8, y);
		ctx.lineTo(x + 4, y + 3);
		ctx.closePath();
		ctx.stroke();
	};

	//	Draw arrow.  This is centered near the base of the arrow...
	//	baseX/Y indicate base of arrow
	//	tx/ty indicate target position
	rat.graphics.drawArrow = function (baseX, baseY, tx, ty, fillStyle, strokeStyle, thick)
	{
		var ctx = rat.graphics.ctx;

		var dx = tx - baseX;
		var dy = ty - baseY;
		var s = Math.sqrt(dx * dx + dy * dy);	//	Length.
		var angle = Math.atan2(dy, dx);
		var t = s / 4;
		//if (typeof thick !== 'undefined')
		//	t = thick;

		rat.graphics.save();
		rat.graphics.translate(baseX, baseY);
		rat.graphics.rotate(angle);

		ctx.beginPath();
		ctx.moveTo(-t, -t);
		ctx.lineTo(-t, t);
		ctx.lineTo(t, t);
		ctx.lineTo(t, 2 * t);
		ctx.lineTo(3 * t, 0);
		ctx.lineTo(t, -2 * t);
		ctx.lineTo(t, -t);
		ctx.closePath();
		ctx.fillStyle = fillStyle;
		ctx.fill();
		ctx.lineWidth = 1;
		ctx.strokeStyle = strokeStyle;
		ctx.stroke();

		rat.graphics.restore();
	};

	//	these suck, and are placeholder for more useful functions
	rat.graphics.drawPlus = function ()
	{
		var ctx = rat.graphics.ctx;
		ctx.fillStyle = "#70B070";
		ctx.fillRect(-7, -3, 14, 6);
		ctx.fillRect(-3, -7, 6, 14);
	};

	rat.graphics.drawMinus = function ()
	{
		var ctx = rat.graphics.ctx;
		ctx.fillStyle = "#C07070";
		ctx.fillRect(-7, -3, 14, 6);
		//ctx.fillRect(-3, -7, 6, 14);
	};

	//	draw parallelogram
	//	upper left position
	//	this is a pretty customized function for Jared, but might be useful in general.
	//	"fillAmount" (0-1) indicates how far up from the bottom to draw the parallelogram, which is a little weird.
	//	"angle" is in radians
	rat.graphics.drawParallelogram = function (x, y, w, h, angle, fillStyle, fillAmount)
	{
		if (typeof (fillAmount) === 'undefined')
			fillAmount = 1;
		if (fillAmount === 0)
			return;

		var ctx = rat.graphics.ctx;

		var xShift = Math.tan(angle) * h;

		var topX = (x + xShift * (1 - fillAmount));
		var topY = (y + (1 - fillAmount) * h);

		ctx.beginPath();
		ctx.moveTo(topX, topY);
		ctx.lineTo(topX + w, topY);

		ctx.lineTo(x + w + xShift, y + h);
		ctx.lineTo(x + xShift, y + h);
		ctx.closePath();

		ctx.fillStyle = fillStyle;
		ctx.fill();

		//ctx.lineWidth = 1;
		//ctx.strokeStyle = strokeStyle;
		//ctx.stroke();

	};

	/**
	 * Draws the line segment or arc.
	 * @param {{center:{x:number, y:number}, radius:number, startAngle:number, endAngle:number, anticlockwise:boolean}|{point1:{x:number, y:number},point2:{x:number, y:number}}} segment represents the arc or strait line.
	 * @param {string|Object} color of the segment.
	 * @param {number=} width of the segment.
	 */
	rat.graphics.drawSegmentOrArc = function (segment, color, width)
	{
		if (color.toString())
			color = color.toString();
		width = width || 1;
		var ctx = rat.graphics.ctx;
		ctx.beginPath();
		if (segment.type === "arc")
			// We are drawing an arc
			ctx.arc(segment.center.x, segment.center.y, segment.radius, segment.startAngle, segment.endAngle, segment.anticlockwise);
		else
		{
			// Line segment
			ctx.moveTo(segment.point1.x, segment.point1.y);
			ctx.lineTo(segment.point2.x, segment.point2.y);
			ctx.closePath();
		}
		//rat.graphics.save();
		ctx.lineWidth = width;
		ctx.strokeStyle = color;
		ctx.stroke();
		//rat.graphics.restore();
	};
	
	/**
	 * Draw an arc (part of a circle)
	 */
	rat.graphics.drawArc = function( cx, cy, r, fromRad, toRad, ops )
	{
		var ctx = rat.graphics.ctx;
		ops = ops || {};
		if (ops.fill)
			ctx.fillStyle = ops.color.toString();
		else
			ctx.strokeStyle = ops.color.toString();
		if (!ops.fill && ops.lineWidth !== void 0)
			ctx.lineWidth = ops.lineWidth;
		
		ctx.beginPath();
		ctx.moveTo( cx, cy );
		ctx.arc(cx, cy, r, fromRad, toRad, true);
		ctx.closePath();
		
		if( ops.fill )
			ctx.fill();
		else
			ctx.stroke();
	};

	/**
	 * Setup the matrix to have any global translation or scale wanted by the game.
	 */
	rat.graphics.setupMatrixForRendering = function ()
	{
		if (rat.graphics.globalTranslate)
			rat.graphics.translate(rat.graphics.globalTranslate.x, rat.graphics.globalTranslate.y);
		if (rat.graphics.hasGlobalScale())
			rat.graphics.scale(rat.graphics.globalScale.x, rat.graphics.globalScale.y);
	};
});

//--------------------------------------------------------------------------------------------------
//
//	rat trackvalue (debug) module
//
//	Track individual values and display them on-screen for debug purposes.
//
//	I found that I kept adding code like this to each game, so I figured this could be a generally
//	useful module.
//
/*
	Notes:
		Generally, use the rat console settings (console color, font size, etc.)

		I'll try to store and restore entries after a refresh,
		But that's tricky.
		
	TODO:
	
		* collect all the value names in an offscreen buffer!
		
*/


rat.modules.add( "rat.debug.r_trackvalue",
[
	"rat.os.r_system", 
	"rat.math.r_math", 
	"rat.graphics.r_graphics", 
	"rat.utils.r_utils", 
	{name:"rat.debug.r_console", processBefore:true},	//	we use registerCommand below

], 
function(rat)
{
	rat.trackValue = {
		entries : [],
		
		state : {
			show : false,
		},
		
		frame : 0,
	};
	
	/*	for reference
	rat.trackValue.config = {
		bounds : {x:48, y:0, w:{fromParent:true, val:-(48*2)}, h:{percent:true, val:0.5}},
		textColor : "#90B090",
		textSize : 12,
		logLineHeight : 14,	//	should be similar to text size
		bgColor : "rgba(0,0,0,0.5)",
	};
	*/
	
	rat.console.registerCommand("track", function (cmd, args)
	{
		rat.trackValue.track(args[0]);
	}, ["trackvalue"]);
	
	rat.console.registerCommand("untrack", function (cmd, args)
	{
		rat.trackValue.unTrack(args[0]);
	});
	
	rat.console.registerCommand("cleartrack", function (cmd, args)
	{
		rat.trackValue.clear();
	}, ["notrack", "trackclear"]);
	
	rat.console.registerCommand("showtrack", function (cmd, args)
	{
		rat.trackValue.show(args[0]);
	}, ["trackshow"]);
	rat.console.registerCommand("hidetrack", function (cmd, args)
	{
		rat.trackValue.show(false);
	}, ["trackhide"]);
	
	
	var storageTrackValueKey = "rat_debugTrackValue";
	
	//	Load the track history from local storage if it is there
	var storage = rat.system.getLocalStorage();
	if (storage)
	{
		var readThis = storage.getItem(storageTrackValueKey);
		if (readThis)
		{
			var readThis = JSON.parse(readThis);
			
			if (readThis && readThis.state)
			{
				rat.trackValue.state = readThis.state;
			}
			if (readThis && readThis.entries)
			{
				rat.trackValue.entries = readThis.entries;
				//	todo : update optimizations, e.g. offscreen values, object references...
				//	for each entry.
			}
		}
	}
	
	rat.trackValue.writeActiveState = function()
	{
		//	now try to save out
		if (rat.system.localStorageObject)
		{
			var storeThis = {
				state : rat.trackValue.state,
				entries : rat.trackValue.entries,
			}
			
			rat.system.localStorageObject.setItem(storageTrackValueKey, JSON.stringify(storeThis));
		}
	};
	
	//	return index of entry, if it's found
	//	otherwise return -1
	rat.trackValue.findEntry = function (expression)
	{
		for (var i = rat.trackValue.entries.length-1; i >= 0; i--)
		{
			if (rat.trackValue.entries[i].expression === expression)
				return i;
		}
		return -1;
	};
	
	//	toggle visibility
	rat.trackValue.show = function (doShow)
	{
		if (doShow === void 0)
			rat.trackValue.state.show = !rat.trackValue.state.show;	//	toggle
		else
			rat.trackValue.state.show = !!doShow;
		
		rat.trackValue.writeActiveState();
	};
	
	//	draw
	//	note that we generally use rat console settings for colors and text.
	rat.trackValue.draw = function ()
	{
		if (!rat.trackValue.state.show)
			return;
	
		//	for laziness, I'm just going to update every time we draw.
		//	todo: We might want to update every nth frame or something...
		rat.trackValue.update(0.01);
	
		var ctx = rat.graphics.ctx;
		var logLineHeight = rat.console.config.logLineHeight;
		var tv = rat.trackValue;
		
		var edgeSize = 5;
		var height = tv.entries.length * logLineHeight + edgeSize * 2;
		var width = 400;
		var x = (rat.graphics.SCREEN_WIDTH - width)/2;
		var bounds = {
			x: x,
			y: rat.graphics.SCREEN_HEIGHT - 40 - height,
			w: width,
			h: height
		};
		
		//	Draw the background
		var bgColor = rat.console.config.bgColor;
		//bgColor.alpha = (bgColor.alpha / 2)|0;	//	fainter background, since it'll be up a lot?
		
		ctx.fillStyle = bgColor.toString();
		ctx.fillRect(bounds.x, bounds.y, bounds.w, bounds.h);
		
		var yPos = bounds.y + edgeSize;
		var xPos = bounds.x + 10;
		var nameWidth = (bounds.w - 50)/2;
		var valueXPos = bounds.x + nameWidth + 25;
		var valueWidth = (bounds.w - 50)/2;
		ctx.textAlign = 'right';
		ctx.textBaseline = 'top';

		//	draw in reverse order so newer strings show up on top,
		//	since we grow our space from the bottom of the screen.
		for (var i = tv.entries.length-1; i >= 0; i--)
		{
			var e = tv.entries[i];
			
			ctx.font = '' + rat.console.config.textSize + 'px Arial';
			ctx.fillStyle = rat.console.config.textColor;
			ctx.fillText(e.displayName, xPos + nameWidth, yPos, nameWidth);
			
			ctx.fillText(e.displayValue, valueXPos + valueWidth, yPos, valueWidth);
			
			yPos += logLineHeight;
		}
		
		//ctx.strokeStyle = "#FFFFFF";
		//ctx.strokeRect(bounds.x, bounds.y, bounds.w, bounds.h);
	};
	
	//	update values for all entries
	//	todo: optionally, per entry, highlight changes in a new color or with a mark.
	rat.trackValue.update = function (dt)
	{
		
		//	OK, seriously, I don't think we need to update very frame.
		var frameCycle = 9;
		var nth = (rat.trackValue.frame % frameCycle);
		
		for (var i = rat.trackValue.entries.length-1; i >= 0; i--)
		{
			if (i % frameCycle !== nth)
				continue;
			
			var e = rat.trackValue.entries[i];
			
			//	not thrilled about this use of eval...
			//	for one thing, it's probably pretty expensive, and we're evaluating
			//	it every frame.
			//	todo: instead of eval every frame,
			//		how about custom code that evaluates the expression down to the parent
			//		object of the last field,
			//		and we keep a ref around to that parent object,
			//		and just evaluate that one field each time, expecting that the object won't change.
			//		maybe even with flags to control this behavior?
			//	todo:
			//		eval once when first added to list (and when list is reloaded on refresh)
			//		and report error then, and don't reevaluate after that if there's an error.
			var value;
			try
			{
				value = eval(e.expression);
			}
			catch (err)
			{
				//rat.console.log("ERROR: Track value evaluation failed: " + err.description);
			}
			
			//	support various formats...
			if (typeof(value) === 'number')
			{
				//	truncate for readability
				value = Math.floor(value * 1000) / 1000;
			}
			e.displayValue = "" + value;
		}
		
		rat.trackValue.frame++;
	};

	//	track this value
	rat.trackValue.track = function (expression)
	{
		if (!expression || typeof(expression) !== 'string')
			return;
		
		//	support toggling, if this exact expression is already found!
		var foundIndex = rat.trackValue.findEntry(expression);
		if (foundIndex > -1)
		{
			rat.trackValue.unTrack(expression);
			return;
		}
		
		var newEntry = {
			expression : expression,
			displayName : "",
			displayValue : "-",
			//	other stuff like flag to highlight change or update less frequently or something?
		};
		
		//	calculate a short version of the expression for displayname
		//	for now, let's just grab a few characters on the right side.
		var goodWidth = 15;
		var displayName = expression.slice(-goodWidth);	//	rightmost characters
		if (displayName.length < expression.length)
			displayName = "…" + displayName;	//	show it was truncated
		newEntry.displayName = displayName;
		
		rat.trackValue.entries.push(newEntry);
		
		//	since we just added something to track, assume they also want it shown.
		rat.trackValue.show(true);
		
		//	rat.trackValue.writeActiveState();	already done in show call above
		
		return true;
	};
	
	//	stop tracking this expression
	rat.trackValue.unTrack = function (expression)
	{
		var foundIndex = rat.trackValue.findEntry(expression);
		if (foundIndex < 0)
		{
			return;	//	no such entry
		}
		
		rat.trackValue.entries.splice(foundIndex, 1);	//	remove it
		
		//	was that our last one?  If so, assume we should hide our display
		if (rat.trackValue.entries.length <= 0)
			rat.trackValue.show(false);
		
		rat.trackValue.writeActiveState();
	};
	
	rat.trackValue.clear = function (expression)
	{
		rat.trackValue.entries = [];	//	clear list
				
		//	since we just cleared our list, assume we also don't want to even try to draw anything.
		rat.trackValue.show(false);
		
		//rat.trackValue.writeActiveState();	//	happens in show call above already
	};
	
	//	hack test
	//rat.trackValue.track("rat.trackValue.entries.length");
	
});
//--------------------------------------------------------------------------------------------------
//
//	Rat Input handling.
//
//	This includes
//		handling input events (keyboard, mouse, touch, controller, whatever)
//		dispatching of input events through screens
//		translation of UI events
//		tracking "allowed controller"
//		associating controllers (and, indirectly, users) with events
//
//	An example scenario for "allowed" tracking
//		Two players sit in front of a single-player game.
//		Each has NUI tracking and a gamepad.  Only one player's inputs should be allowed.
//		The other player then picks up the first player's controller.  Now only his inputs should be allowed.
//		Weird, I know.
//
//	controller IDs are tracked differently depending on system.
//	On a browser, and other systems by default, mouse+keyboard are ID 0
//	On an Xbox, controller ID is the Xbox Controller ID
//	We do not track indices, in case one input is added or another removed.

//	Rat events vs. system events:
//		Once events come in from the system, we create a "ratEvent", which usually includes the original
//		system event ("sysEvent"), and some extra info.  We pass that rat event around from then on,
//		instead of a raw system event.
//		This lets us pass extra rat-specific and augmentive data around with the event,
//		and doesn't require us to modify the system event, which really should be read only.
//
//	DefaultEvents and prevention (2016.2.4)
//		Normally, we allow default event handling by the browser.
//			(though, you can change that base behavior by simply calling rat.input.setStandardPreventFlag())
//
//		Also, if you explicitly say you didn't handle an event (returned false from handler), then we allow default,
//			because if YOU didn't handle it, then you don't care about it, and it's OK for the browser to handle it.
//
//		If you set the allowBrowserDefault variable on the rat event, then we respect whatever you set it to, and do that.
//
//		So, if you want to explicitly say you handled the event and ALSO control whether default browser handling is allowed,
//		then return true from handler, and set the rat event's "allowBrowserDefault" variable.
//
//		see "handlePreventDefault()"!
//		This is all very confusing, and we're OK with rethinking it, but we think it's pretty good for now!
//		Talk to STT or JHS
//		

// Uses:
// rat.system, rat.graphics, rat.screenManager, rat.audio
rat.modules.add( "rat.input.r_input",
[
	"rat.debug.r_console",
	"rat.os.r_system",
	"rat.math.r_math",
	"rat.utils.r_utils",
	"rat.graphics.r_graphics",
	"rat.input.r_voice",
	"rat.input.r_keyboard",
	
	{name: "rat.input.r_input_xbo", platform: "xbox"}, // platform specific versions run AFTER me
	{name: "rat.input.r_input_le", platform: "xboxLE"}, // platform specific versions run AFTER me
], 
function(rat)
{	
	//rat.console.log("SLD rat.input");
	rat.input = {};
	
	var useLEGamepadAPI = false;
	var fakeGamepadAPI = false;

	//  this is the beginning of an attempt to track input type used by the user
	//  and automate a few things more nicely by looking at that.
	//  For instance, if the user is actively using keyboard to navigate UI,
	//      then we should select a button by default in an input map.
	//      But if they're using the mouse, we should not, so it won't highlight before they mouse over it.
	//  This feature is not yet thoroughly implemented.  Feel free to work on it!  :)
	//  (for now, I'm trying to get mouse/keyboard swapping to work well)
	//  Note that this just tracks UI input for now, since a mouse+keyboard combo may be desired,
	//  and we don't want to keep flopping modes, and it's easier to understand what UI navigation inputs are.
	//  Also, this feature has to be turned on explicitly, since a particular game may not want this at all,
	//  e.g. if they use arrow keys for something other than UI tracking, and mouse for UI.
	//
	//  Right now we assume ANY mouse input means use mouse UI.
	//
	//  This is actually pretty complicated... if the game doesn't support full keyboard UI,
	//      then this doesn't work well, 'cause mouse movement resets to mouse mode.
	rat.input.useLastUIInputType = false;
	rat.input.lastUIInputType = 'mouse';  //  'mouse', 'keyboard', 'controller', 'voice'

	// allow holding a button down to continue getting the same event - currently only checked in XBO the keyboard seems to handle
	// things differently and forces out a new event no matter what without checking previous states
	rat.input.allowRepeatEvents = {
		buttons: false,  /**  I think most games don't want buttons to repeat.
 */
		directions: true /**  but almost all games do want directions to repeat
 */
	};

	//	Allow preventing default on current event being handled
	
	//	this one is a way to set all of rat's event handling - do we normally prevent default or not?  By default, we DO prevent default
	//	(because default handling often messes with games)
	rat.input.standardPreventDefaultOnEvents = false;
	
	//	and this one says what to do with the current event (changed on an event by event basis)
	//	Why is this a separate variable instead of being part of the current rat event object?
	//	because in some cases below, that rat event object doesn't exist yet, or doesn't exist outside a certain context,
	//	so, another approach here would be to create the rat event pretty early and always have it to flag instead of using this var...
	//rat.input.preventDefaultOnEvents = rat.input.standardPreventDefaultOnEvents;
	
	rat.input.allowedControllers = [];				//	which controller inputs are allowed to trigger standard UI events.  blank list = allow all.

	//	this "active controller" concept is questionable - what about local multiplayer?   Use your own userIDs and controllerIDs instead
	rat.input.activeControllerID = 0;				//	by default, keyboard/mouse

	/**
	 * Array of currently connected controllers
	 * @type Array.<rat.input.Controller>
	 */
	rat.input.controllers = [];
	rat.input.controllers.getByID = function (id)
	{
		for (var index = 0; index < rat.input.controllers.length; ++index)
		{
			if (rat.input.controllers[index].id === id)
				return rat.input.controllers[index];
		}
		return void 0;
	};
	
	//	fake controller inputs from keyboard?  See below.
	rat.input.gamepadKeyFakeEnabled = false;

	//	standard rat button definitions; in order to generically support controllers independent of system
	rat.input.BUTTON_UP = 0x00000001;		//	simple mapped inputs, could be from dpad or from (left) stick
	rat.input.BUTTON_DOWN = 0x00000002;
	rat.input.BUTTON_LEFT = 0x00000004;
	rat.input.BUTTON_RIGHT = 0x00000008;

	rat.input.BUTTON_START = 0x00000010;
	rat.input.BUTTON_SELECT = 0x00000020;

	rat.input.BUTTON_A = 0x00000100;
	rat.input.BUTTON_B = 0x00000200;
	rat.input.BUTTON_C = 0x00000400;
	rat.input.BUTTON_D = 0x00000800;
	rat.input.BUTTON_X = rat.input.BUTTON_C;
	rat.input.BUTTON_Y = rat.input.BUTTON_D;

	rat.input.BUTTON_LT = 0x00001000;
	rat.input.BUTTON_LB = 0x00002000;
	rat.input.BUTTON_RT = 0x00004000;
	rat.input.BUTTON_RB = 0x00008000;

	rat.input.BUTTON_DPAD_UP = 0x00010000;		//	these are explicitly from a dpad; not ever mapped from stick
	rat.input.BUTTON_DPAD_DOWN = 0x00020000;
	rat.input.BUTTON_DPAD_LEFT = 0x00040000;
	rat.input.BUTTON_DPAD_RIGHT = 0x00080000;
	
	rat.input.BUTTON_LEFT_STICK = 0x00100000;
	rat.input.BUTTON_RIGHT_STICK = 0x00200000;
	
	rat.input.BUTTON_LSTICK_UP		= 0x01000000;
	rat.input.BUTTON_LSTICK_DOWN	= 0x02000000;
	rat.input.BUTTON_LSTICK_LEFT	= 0x04000000;
	rat.input.BUTTON_LSTICK_RIGHT	= 0x08000000;
	rat.input.BUTTON_RSTICK_UP		= 0x10000000;
	rat.input.BUTTON_RSTICK_DOWN	= 0x20000000;
	rat.input.BUTTON_RSTICK_LEFT	= 0x40000000;
	rat.input.BUTTON_RSTICK_RIGHT	= 0x80000000;

	rat.input.BUTTON_COUNT = 32;	//	bits/buttons supported

	// gamePadAPI button mapping (matches the gamepad.buttons order)
	// Axis/button mapping came from here
	// http://www.html5rocks.com/en/tutorials/doodles/gamepad/
	// Says index 1 is button 2 and index 2 is button 3 even though it's pointing is switched from that.
	// But testing the gamepad at http://html5gamepad.com/
	// in Chrome Version 35.0 and Firefox 30.0 shows that B is 1 and X is 2.
	rat.input.GAMEPAD_CONTROLLER_MAPPING = {
		//	Order matches the order the gamepad.buttons field
		//	positive values are in the buttons array
		//	negative values are in the axes array and negative one (not zero) based.
		BUTTON_A: 0,
		BUTTON_B: 1,
		BUTTON_C: 2,
		BUTTON_X: 2,// Same as C
		BUTTON_D: 3,
		BUTTON_Y: 3,// Same as D
		BUTTON_LB: 4,
		BUTTON_RB: 5,
		BUTTON_LT: 6,
		BUTTON_RT: 7,
		BUTTON_SELECT: 8,
		BUTTON_START: 9,
		BUTTON_LEFT_STICK: 10,
		BUTTON_RIGHT_STICK: 11,
		BUTTON_DPAD_UP: 12,
		BUTTON_DPAD_DOWN: 13,
		BUTTON_DPAD_LEFT: 14,
		BUTTON_DPAD_RIGHT: 15,
		leftStick: {
			x: -1,
			y: -2
		},
		rightStick: {
			x: -3,
			y: -4,
		},
		leftTrigger: 6,
		rightTrigger: 7
	};

	/**
	 * Enum for controller change type
	 */
	rat.input.ControllerChangeType = {
		REMOVED: 0,
		ADDED: 1,
		UPDATED: 2
	};

	/**
	 * @suppress {missingProperties}
	 */
	rat.input.init = function ()
	{
		// Polling is running... Yet
		var rInput = rat.input;
		var rHas = rat.system.has;
		rInput.controllers.pollingForced = false;
		rInput.controllers.pollingAllowed = false;

		if (rHas.xbox)
		{
			rat.input.initXbox();
		}
		else if (rHas.xboxLE)
		{
			if (useLEGamepadAPI)
				rat.input.initXboxLE();
		}
		
		//	include gamepad support if we are not getting it from the xbox
		else if(rHas.gamepadAPI)
		{
			rInput.controllers.pollingAllowed = true;
			if(rHas.gamepadAPIEvent)
			{
				window.addEventListener("gamepadconnected", rInput.onSysControllerAdded);
				window.addEventListener("gamepaddisconnected", rInput.onSysControllerRemoved);
			}
			else
				rInput.controllers.pollingForced = true;

			//	Assign the function that we use to get the controller list
			var nav = navigator;
			var platformGetGamepadsFunc = nav.getGamepads ||
							nav.mozGetGamepads ||
							nav.webkitGetGamepads ||
							function ()
							{
								return nav.gamepads ||
									   nav.mozGamepads ||
									   nav.webkitGamepads ||
										[];
							};
			var getGamepadsFunc = platformGetGamepadsFunc;
			
			//  Derek: For builds that names controllers of the same type with the same id, append the controller id with the index.
			if(rat.system.has.chromeBrowser)
			{
				getGamepadsFunc = function()
				{
					var gamepads = platformGetGamepadsFunc.call(this);
					var gamepadsWithUniqueId = [];
					
					for(var i = 0; i < gamepads.length; ++i) {
						var gamepad = gamepads[ i ];
						if(gamepad != null) {
							var gamepadWithUniqueId = {}; // Since the system's gamepad data cannot be modified, copy the data to a new object.
							for(var key in gamepad) {
								gamepadWithUniqueId[ key ] = gamepad[ key ];
							}
							gamepadWithUniqueId.id += gamepadWithUniqueId.index; // Modify the id to make it unique.
							gamepadsWithUniqueId.push(gamepadWithUniqueId);
						}
						else {
							gamepadsWithUniqueId.push(null);
						}
					}
					
					return gamepadsWithUniqueId;
				};
			}
							
			var wraithGamepadSort = function(a, b)
			{
				return (!a) ? -1 : (!b) ? 1 : b.index - a.index;
			};
			
			rInput.controllers.getSystemControllers = function ()
			{
				var gamepads = getGamepadsFunc.call(nav);

				//	Get gamepads as a true array
				if (Array.isArray(gamepads) === false)
				{
					var newList = [];
					for (var index = gamepads.length - 1; index >= 0; --index)
						newList.unshift(gamepads[index]);
					gamepads = newList;
				}

				//	optional fake gamepad support
				if (rat.input.gamepadKeyFakeEnabled && !rat.console.state.consoleActive )
				{
					gamepads.push(rat.input.buildGamepadKeyFake());
				}

				/**                                                
				    Most of the time we sort the gamepad list
				  	Sort the list of gamepads from the system by their index (higher indexes go last)
 */
				if (!rat.system.has.Wraith)
				{
					gamepads = gamepads.sort(wraithGamepadSort);
				}

				return gamepads;
			};
		}
		else
		{
			rInput.controllers.pollingAllowed = true;
			rInput.controllers.pollingForced = true;
			fakeGamepadAPI = true;
			rInput.controllers.getSystemControllers = function ()
			{
				var gamepads = [];
				if (rat.input.gamepadKeyFakeEnabled)
				{
					gamepads.push(rat.input.buildGamepadKeyFake());
				}
				return gamepads;
			}
		}
	};

	//	add mouse handler to get called whenever we get any mouse or translated touch event,
	//	right before it's handed off to the screen manager.
	//	This is useful for filtering out click events for some reason (return true to indicate handled)
	//	or for getting clicks outside our UI space, etc.
	rat.input.setMouseHandler= function (callback)
	{
		rat.input.mouseHandler = callback;
	};

	//	Update input handling.
	rat.input.update = function (dt)
	{
		var rInput = rat.input;
		//	Update the controllers if we need to.
		if(rInput.controllers.pollingAllowed &&
			(rInput.controllers.pollingForced || rInput.controllers.length > 0))
		{
			rInput.updateControllers(dt);
		}
		//	update keyboard.
		rat.input.keyboard.update(dt);
	};

		//	debounce buttons (find which are new)
	rat.input.debounceButtons = function (cont)
	{
		cont.newButtons = cont.rawButtons & ~cont.lastButtons;
		cont.lastButtons = cont.rawButtons;
	};

	// Get a controller by its ID
	rat.input.getControllerByID = function (id)
	{
		var controller;
		for (var ecIndex = 0; ecIndex < rat.input.controllers.length; ecIndex++)
		{
			controller = rat.input.controllers[ecIndex];
			if (controller.id === id)
				return controller;
		}
		return void 0;
	};

	//	get "current" active single player controller.  This is pretty questionable, but let's do it for now,
	//	until we have better code for really knowing which user is playing, and which controller is his.
	rat.input.getActiveController = function ()
	{
		//	if no controllers, return null
		if(rat.input.controllers.length <= 0)
			return null;

		//	First, try to find the currently set active controller
		var ecIndex;
		if (rat.input.activeControllerID !== 0)
		{
			for (ecIndex = 0; ecIndex < rat.input.controllers.length; ecIndex++)
			{
				if (rat.input.controllers[ecIndex].id === rat.input.activeControllerID)
					return rat.input.controllers[ecIndex];
			}
		}

		//	If we cannot find it, or if we don't have an active controller
		//	temp - handle the case of no active controller id by returning the first one.
		//	NOT good for long-term use.  TODO: fix this.  tighten up allowed/active controllers.
		var found = void 0;
		for (ecIndex = 0; ecIndex < rat.input.controllers.length; ecIndex++)
		{
			if (!found || found.index > rat.input.controllers[ecIndex].index)
				found = rat.input.controllers[ecIndex];
		}

		//	Return the controller with the lowest index
		return found;
	};

	function accumulateControllerInput( masterObject, addedObject )
	{
		masterObject.lastButtons |= addedObject.lastButtons;
		masterObject.newButtons |= addedObject.newButtons;
		masterObject.rawButtons |= addedObject.rawButtons;

		masterObject.leftStick.x += addedObject.leftStick.x;
		masterObject.leftStick.y += addedObject.leftStick.y;
		masterObject.rightStick.x += addedObject.rightStick.x;
		masterObject.rightStick.y += addedObject.rightStick.y;
		if (addedObject.leftTrigger > masterObject.leftTrigger)
			masterObject.leftTrigger = addedObject.leftTrigger;
		if (addedObject.rightTrigger > masterObject.rightTrigger)
			masterObject.rightTrigger = addedObject.rightTrigger;
		// handle averaging left and right stick? trigger buttons?
	};
	
	function normalizeStick( stick )
	{
		stick.x = rat.math.clamp( stick.x, -1, 1 );
		stick.y = rat.math.clamp( stick.y, -1, 1 );
		var len = rat.math.sqrt(stick.x * stick.x + stick.y * stick.y);
		if (len > 1)
		{
			stick.x /= len;
			stick.y /= len;
		}
	}
	
	rat.input.getCombinedControllers = function ( controllerIdList )
	{
		//	if no controllers, return null
		if(rat.input.controllers.length <= 0)
			return null;
			
		var combinedControllerObj = new rat.input.Controller({}, "COMBINED", -1, true, 0);
				
		// if there is no passed in controller list, then make the list the allowed controllers
		if( !controllerIdList || controllerIdList.length === 0 )
			controllerIdList = rat.input.allowedControllers;
			
		if( controllerIdList && controllerIdList.length > 0 )
		{
			for (var i = 0; i < controllerIdList.length; i++)
			{
				var cntrl = rat.input.controllers.getByID(controllerIdList[i]);
				accumulateControllerInput(combinedControllerObj, cntrl);
				
			}
		}
		else if( rat.input.allowedControllers.length === 0 || rat.input.activeControllerID === 0 )
		{
			// if no list was passed in and all controllers are allowed, the list will still be empty, so handle that case
			for (var j = 0; j < rat.input.controllers.length; j++)
				accumulateControllerInput(combinedControllerObj, rat.input.controllers[j]);
		}
		
		normalizeStick( combinedControllerObj.leftStick );
		normalizeStick( combinedControllerObj.rightStick );

		return combinedControllerObj;
	};
	
	rat.input.getActiveControllerID = function ()
	{
		//	if no controllers, return null
		var controller = rat.input.getActiveController();

		if(!controller)
			return null;

		return controller.id;
	};

	//	Set the active controller ID
	rat.input.setActiveControllerID = function ( id )
	{
		id = id || 0;
		if (!id)
		{
			rat.console.log("Cleared active controller");
			rat.input.activeControllerID = id;
			return true;
		}
		else
		{
			for (var cIndex = 0; cIndex < rat.input.controllers.length; cIndex++)
			{
				if (rat.input.controllers[cIndex].id === id)
				{
					rat.console.log("Setting active controller to " + id);
					rat.input.activeControllerID = id;
					return true;
				}
			}
		}
		
		rat.console.log("WARNING! Attempting to set active controller ID for controller "+ id +" which cannot be found in the system" );
		rat.input.activeControllerID = 0;
		return false;
	};

	rat.input.getActiveControllerRawButtons = function ()
	{
		var buttons = 0;
		var controller = rat.input.getActiveController();
		if(controller)
			buttons = controller.rawButtons;
		return buttons;
	};

	//
	//	Get mouse position relative to context.
	//	This factors in browser differences...
	//http://answers.oreilly.com/topic/1929-how-to-use-the-canvas-and-draw-elements-in-html5/
	//http://developer.appcelerator.com/question/55121/html5-canvas-drawing-in-webview
	//
	rat.input.getRealMousePosition = function (e)
	{
		var pos = new rat.Vector();

		if(e.pageX || e.pageY)
		{
			pos.x = e.pageX;
			pos.y = e.pageY;
		}
		else
		{
			pos.x = e.clientX + document.body.scrollLeft +
					document.documentElement.scrollLeft;
			pos.y = e.clientY + document.body.scrollTop +
					document.documentElement.scrollTop;
		}
		pos.x -= rat.graphics.canvas.offsetLeft;
		pos.y -= rat.graphics.canvas.offsetTop;

		//	apply global translation
		if(rat.graphics.globalTranslate)
		{
			pos.x -= rat.graphics.globalTranslate.x;
			pos.y -= rat.graphics.globalTranslate.y;
		}

		//	apply global scale if there is one
		//	(this goes backwards because the user is moving in screen space and needs to be translated out to our virtual space)
		if (rat.graphics.hasGlobalScale())
		{
			pos.x /= rat.graphics.globalScale.x / rat.graphics.canvasPixelRatio;
			pos.y /= rat.graphics.globalScale.y / rat.graphics.canvasPixelRatio;
		}

		//	keep constant track of last known global (within context) mouse pos
		rat.mousePos.x = pos.x;
		rat.mousePos.y = pos.y;

		return pos;
	};

	//	see if this controller id is in our list of allowed controller ids
	rat.input.controllerIDAllowed = function (id)
	{
		if(rat.input.allowedControllers.length === 0)	//	no list means all allowed
			return true;

		for(var i = 0; i < rat.input.allowedControllers.length; i++)
		{
			if(rat.input.allowedControllers[i] === id)
				return true;
		}
		return false;
	};

	/**  Get the which value from a system event object
 */
	rat.input.getEventWhich = function (e)
	{
		if (rat.system.has.xbox )
		{
			//	Several keys on the xbox map to strange values.  We fix that mapping here.
			switch( e.which )
			{
				case 222: return 220;
				case 223: return 192;
				case 192: return 222;
			}
		}
		return e.which || 0;
	};

	/**
	 * Create a new rat event object
	 * @param {?} sysEvent
	 * @param {Object=} options
	 * @constructor
	 */
	rat.input.Event = function (sysEvent, options)
	{
		this.sysEvent = sysEvent;
		options = options || {};
		sysEvent = sysEvent || {};

		if (options.translatedFrom)
		{
			var savedSysEvent = options.translatedFrom.sysEvent;
			options.translatedFrom.sysEvent = void 0;
			rat.utils.extendObject(this, [options.translatedFrom], false);
			options.translatedFrom.sysEvent = savedSysEvent;

			this.translatedFrom = options.translatedFrom;

			//	Allow overwriting the which value
			if (options.which !== void 0)
				this.which = options.which;
		}

		//	Set this AFTER we extended so we can override anything we copied.
		this.eventType = options.type || '';

		if (this.controllerID === void 0)
			this.controllerID = sysEvent.deviceSessionId || sysEvent.controllerID || options.defaultControllerID || 0;
		if (this.index === void 0)
			this.index = options.index || 0;
		if (this.which === void 0)
		{
			this.which = options.which;
			if (this.which === void 0)
				this.which = rat.input.getEventWhich( sysEvent );
		}
		if (this.repeat === void 0)
			this.repeat = options.repeat || false;
		
		//	for debugging(?), track unique event ID
		this.eventID = rat.input.Event.nextID++;
	};
	rat.input.Event.nextID = 1;


	//	if this controller's inputs suggest it, then dispatch events
	rat.input.checkAndDispatchControllerEvents = function (controller, dt)
	{
		//	Here is the place to NOT dispatch events for non-allowed controllers
		if(!rat.input.controllerIDAllowed(controller.id))
			return;

		var ratEvent = void 0;
		//	Type and which are set later
		var btnFlag;
		var btnStr;
		var fullStr = "00000000";
		var bIndex;

		//	If any are newly pressed or released, re-set the repeat timer.
		if (controller.rawButtons !== controller.lastButtons || controller.rawButtons === 0)
			controller.repeatTimer.fullReset();
		else
			controller.repeatTimer.elapsed(dt);

		//	Fire all the new button events
		if(controller.newButtons)
		{
			for(bIndex = 0; bIndex < rat.input.BUTTON_COUNT; bIndex++)
			{
				btnFlag = (1 << bIndex);
				if((controller.newButtons & btnFlag) !== 0)
				{
					btnStr = btnFlag.toString(16);
					btnStr = fullStr.substr(fullStr.length - btnStr.length - 1) + btnStr;

					//	Create the first time only.
					ratEvent = ratEvent || new rat.input.Event({ controllerID: controller.id }, { index: controller.index });

					//	Fire the appropriate event.
					ratEvent.eventType = 'buttondown';
					ratEvent.which = btnFlag;
					//rat.console.log("Firing " + ratEvent.eventType + " for btn 0x" + btnStr);
					rat.input.dispatchEvent(ratEvent);
				}
			}
		}

		//	Find out which buttons just got released by comparing raw with last
		var isDown, wasDown;
		if(controller.rawButtons !== controller.lastButtons)
		{
			for(bIndex = 0; bIndex < rat.input.BUTTON_COUNT; bIndex++)
			{
				btnFlag = (1 << bIndex);
				isDown = (controller.rawButtons & btnFlag) !== 0;
				wasDown = (controller.lastButtons & btnFlag) !== 0;
				if(wasDown === true && isDown === false)
				{
					//btnStr = btnFlag.toString(16);
					//btnStr = fullStr.substr(fullStr.length - btnStr.length - 1) + btnStr;

					//	Create the first time only.
					ratEvent = ratEvent || new rat.input.Event({ controllerID: controller.id }, { index: controller.index });

					//	Fire the appropriate event.
					ratEvent.eventType = 'buttonup';
					ratEvent.which = btnFlag;
					//rat.console.log("Firing " + ratEvent.eventType + " for btn " + btnFlag.toString(16));
					rat.input.dispatchEvent(ratEvent);
				}
			}
		}


		//	Now trigger repeat events if the timers allow it
		var repeatButtons = controller.repeatTimer.buttons <= 0 && rat.input.allowRepeatEvents.buttons;
		var repeatDirections = controller.repeatTimer.directions <= 0 && rat.input.allowRepeatEvents.directions;
		if (repeatButtons || repeatDirections)
		{
			var ops = {};
			if (repeatButtons)
				ops.buttons = true;
			if (repeatDirections)
				ops.directions = true;
			controller.repeatTimer.repeatReset(ops);

			if (ratEvent)
				ratEvent.repeat = true;
			for (bIndex = 0; bIndex < rat.input.BUTTON_COUNT; bIndex++)
			{
				btnFlag = (1 << bIndex);
				isDown = (controller.rawButtons & btnFlag) !== 0;
				if (!isDown)
					continue;
				var isDirection = (btnFlag & (rat.input.BUTTON_UP | rat.input.BUTTON_DOWN | rat.input.BUTTON_LEFT | rat.input.BUTTON_RIGHT)) !== 0;
				if ((isDirection && !repeatDirections) ||
					(!isDirection && !repeatButtons))
					continue;

				//	Create the first time only.
				if (!ratEvent)
				{
					ratEvent = new rat.input.Event({ controllerID: controller.id }, { index: controller.index });
					ratEvent.repeat = true;
				}

				ratEvent.eventType = 'buttondown';
				ratEvent.which = btnFlag;
				rat.input.dispatchEvent(ratEvent);
			}
		}
	};

	//	People who want to handle events from rat
	var eventHandlers = [];
	
	//	Add a new event handler
	/** @param {Object=} thisObj */
	rat.input.registerEventHandler = function (func, thisObj)
	{
		if (func)
			eventHandlers.push({ func: func, thisObj: thisObj });
	};

	//	Remove an event handler
	/** @param {Object=} thisObj */
	rat.input.unRegisterEventHandler = function (func, thisObj)
	{
		if (func)
		{
			for (var index = 0; index !== eventHandlers.length; ++index)
			{
				if (eventHandlers[index].func === func && eventHandlers[index].thisObj === thisObj)
				{
					eventHandlers.splice(index, 1);
					return;
				}
			}
		}
	};

	//	Dispatch this event to screen manager or anyone else registered as an eventhandler.
	//	(see screemanager class)
	//	Try sending event.  If it's not handled, translate to UI event and try again.
	//	(this is the main job of this function).
	// PMM: for good measure I added checks so we can let the caller know if the event was consumed by this dispatch
	rat.input.dispatchEvent = function (ratEvent)
	{
		//	First pass.  Let everyone try to handle the raw event
		var handler;
		var gotHandledFALSE = false;
		for (var index = -1; index !== eventHandlers.length; ++index)
		{
			if (index === -1)
			{
				if (rat.console.state.consoleAllowed)
					handler = { func: rat.console.handleEvent };
				else
					continue;
			}
			else
				handler = eventHandlers[index];

			//	If it is handled, abort.
			var handled = handler.func.call(handler.thisObj, ratEvent);
			if (handled)
				return true;
			else if(handled !== void 0)
				gotHandledFALSE = true;
		}

		//	If it wasn't handled yet, see if this can be interpreted as UI input,
		//  and if so, try again (dispatch again)
		if (ratEvent.eventType !== "ui")
		{
			var uiEvent = rat.input.translateToUIEvent(ratEvent);
			if (uiEvent)
				return rat.input.dispatchEvent(uiEvent);
		}

		//	This has to do with if we want to prevent default browser event handling
		//	See comments at the top of the file, and handlePreventDefault
		if( gotHandledFALSE)
			return false;
		else
			return void 0;
	};

	//	process this key event and make sure event.which has the key code in it, regardless of browser.
	//	TODO:  Is it OK to modify the system event like this?  Instead move this value to rat event,
	//		which already is set up to have a copy of "which"
	rat.input.standardizeKeyEvent = function (event)
	{
		//	see http://unixpapa.com/js/key.html which seems to be definitive
		//	and http://stackoverflow.com/questions/7542358/actual-key-assigned-to-javascript-keycode
		//	and http://stackoverflow.com/questions/4471582/javascript-keycode-vs-which among others

		if(event.which === null)
		{
			if(event.charCode)
				event.which = event.charCode;
			else if(event.keyCode)
				event.which = event.keyCode;
			else
				event.which = 0;	//	special key of some kind... ignore - could be shift/control key, for instance, for a keypress
		}
		//else {
		//	event.which is OK
		//}
	};

	//	give me a character code from this event
	//	(useful instead of hard-coded key codes)
	//	also note:  use keypress when you can, since it works better with non-US keyboards
	//	assumes standardizeKeyEvent has been called, or event.which is otherwise valid
	rat.input.charCodeFromEvent = function (event)
	{
		return String.fromCharCode(event.which).toLowerCase();
	};

	/**
	 * Convert a mouse event to a rat mouse event
	 * @param {?} e systemEventObject
	 * @param {string} eventType
	 * @param {boolean=} isFromTouch  Is this a touch event.
	 * @suppress {missingProperties} 
	 */
	rat.input.mouseToRatEvent = function (e, eventType, isFromTouch)
	{
		isFromTouch = isFromTouch || false;
		var pos = rat.getRealMousePosition(e);
		rat.graphics.cursorPos.x = pos.x;
		rat.graphics.cursorPos.y = pos.y;

		//	set up a rat event, with system event attached.
		//	This is a cleaner approach than tacking my own variables on to
		//	a system event, which really ought to be read-only.
		var ratEvent = new rat.input.Event(e, { type: eventType, defaultControllerID: 'mouse' });
		ratEvent.pos = pos;
		ratEvent.isFromTouch = isFromTouch;//	remember if this was translated from touch
		//	pointerID distinguishes between multiple touches (fingers) or from devices.
		if (e.pointerId !== void 0)
			ratEvent.pointerID = e.pointerId;
		else
			ratEvent.pointerID = -1;

		//	handle touch radius stuff...
		if(isFromTouch)
		{
			// units are not documented...  Let's say they're pixels
			//	pick a decent default for finger size.
			//	This is totally kludged.  Should be based on tons of things, like pixel density on device...
			var defRadius = 12;
			//	factor in UI scale
			if(rat.graphics.hasGlobalScale())
				defRadius /= rat.graphics.globalScale.x / rat.graphics.canvasPixelRatio;

			var rx = e.webkitRadiusX;
			if(!rx || rx === 1)	//	this is not very useful
				rx = defRadius;
			ratEvent.touchRadiusX = rx;
			var ry = e.webkitRadiusY;
			if(!ry || ry === 1)	//	this is not very useful
				ry = defRadius;
			ratEvent.touchRadiusY = ry;
		}

		var handled = false;
		//	check for custom global mouse handler (See setMouseHandler above)
		if(rat.input.mouseHandler)
		{
			handled = rat.input.mouseHandler(ratEvent);
		}

		if (!handled)
			handled = rat.input.dispatchEvent(ratEvent);
		
		//	in order to pass back both the "handled" concept AND the event,
		//	let's put handled status in the event and return the event.
		//	we might also later want that "handled" to be in there anyway, later.
		ratEvent.handled = handled;
		return ratEvent;
	};

	//	my built-in event handling functions
	rat.input.onMouseMove = function (e)
	{
		//	this is a little bit of a kludge.
		//	TODO:  Figure this out.  But I already spent hours on it...
		//	In Win 8, we get mouse move events on the whole screen even when app bar is active.
		//		but we don't get mouse up/down, which ends up looking pretty broken (buttons highlight but don't click)
		//		and it causes other problems.
		//		So, support explicit appbar check here...
		//	What would be a lot better would be to GET the dang down/up events for space outside the appbar div itself,
		//	but I can't figure out how to get those events.  They just get eaten by the system.  :(
		//	TODO:  Look at WinJS code?  For whatever reason, it must be calling preventDefault, or otherwise not passing on that event,
		//		but why it does it for up/down and not for move, I have no idea.
		if (rat.input.appBar && !rat.input.appBar.hidden)
			return;

		//  track what was last used.  Note:  This could instead be set when the user clicks on a UI element,
		//  instead of any mouse motion?  But probably any mouse movement right now means don't be assuming keyboard UI.
		//  see useLastUIInputType
		rat.input.lastUIInputType = 'mouse';
		var isTouch = e.pointerType === "touch" || e.pointerType === 0x00000002 || e.pointerType === 0x00000003;
		var ratEvent = rat.input.mouseToRatEvent(e, 'mousemove', isTouch);
		
		rat.input.handlePreventDefault(ratEvent, ratEvent.handled);
	};
	rat.input.onMouseDown = function (e)
	{
		if (rat.input.appBar && !rat.input.appBar.hidden)
			return;
		
		//	sad, but if we're targetting kong, need to try to maintain focus on every click.
		if (rat.system.kong)
			rat.system.kong.focus();
		
		var isTouch = e.pointerType === "touch" || e.pointerType === 0x00000002 || e.pointerType === 0x00000003;
		var ratEvent = rat.input.mouseToRatEvent(e, 'mousedown', isTouch);
		rat.input.handlePreventDefault(ratEvent, ratEvent.handled);
	};
	rat.input.onMouseUp = function (e)
	{
		if (rat.input.appBar && !rat.input.appBar.hidden)
			return;
		var isTouch = e.pointerType === "touch" || e.pointerType === 0x00000002 || e.pointerType === 0x00000003;
		var ratEvent = rat.input.mouseToRatEvent(e, 'mouseup', isTouch);
		rat.input.handlePreventDefault(ratEvent, ratEvent.handled);
	};

	rat.input.onMouseWheel = function (e)
	{
		var event = window.event || e; // old IE support

		//	calculate a number of clicks.
		//	See http://www.javascriptkit.com/javatutors/onmousewheel.shtml among other descriptions of how this works.
		//	This has changed over time.  Various browsers return various scales and values.
		//	This could use some more research.  Find out what the latest standard is (maybe "wheel"?)
		//	and use that.  Note that even then, the values returned are inconsistent...
		//	chrome returns in "wheelDelta" 120 per click, +120 being up
		//	We normalize to 1 per click, and we treat +1 as "up")
		//
		var delta = 0;
		if (event.wheelDelta)
			delta = event.wheelDelta / (120);
		else if (event.deltaY)
			delta = -event.deltaY/3;
		else if (event.detail !== void 0)
			delta = -event.detail;
		else
			delta = 0;

		//console.log("delta " + delta + "(" + event.wheelDelta + ")");

		var ratEvent = new rat.input.Event(e, { type: 'mousewheel', defaultControllerID: 'mouse' });
		ratEvent.wheelDelta = delta;
		//	we want wheel events to get handled based on visual target,
		//	e.g. what user is hovering over.
		//	wheel events (at least in chrome) don't have a pos.
		//	so, use whatever we know!
		ratEvent.pos = {x:rat.mousePos.x, y:rat.mousePos.y};
		rat.input.dispatchEvent(ratEvent);
	};

	rat.input.onKeyPress = function (e)
	{
		e.char = e.char || String.fromCharCode(e.keyCode || e.charCode);
		if (!e.char)
			return;
		var ratEvent = new rat.input.Event(e, { type: 'keypress', defaultControllerID: 'keyboard' });
		var handled = rat.input.dispatchEvent(ratEvent);

		rat.input.handlePreventDefault(ratEvent, handled);
	};

	rat.input.onKeyDown = function (e)
	{
		//rat.console.log("rinput.keydown " + e.which);
		// update keyboard info if applicable
		rat.input.keyboard.handleKeyDown(e);			//	track keyboard state
		var ratEvent = new rat.input.Event(e, { type: 'keydown', defaultControllerID: 'keyboard' });
		var handled = rat.input.dispatchEvent(ratEvent);

		// update key presses for gamepad info if applicable
		if (!useLEGamepadAPI)
		{
			if (e.deviceSessionId && rat.system.has.xboxLE)
			{
				var ourController = rat.input.getControllerInfo(e.deviceSessionId);
				if(ourController)
					rat.input.handleGamepadDownEvent(ratEvent, ourController);
			}
		}

		rat.input.handlePreventDefault(ratEvent, handled);
	};

	rat.input.onKeyUp = function (e)
	{
		//console.log("key up " + e.which);
		rat.input.keyboard.handleKeyUp(e);
		var ratEvent = new rat.input.Event(e, { type: 'keyup', defaultControllerID: 'keyboard' });
		var handled = rat.input.dispatchEvent(ratEvent);

		if (!useLEGamepadAPI)
		{
			//update key presses for gamepad info if applicable	NOTE: we may want to check to see if dispatched used the event, otherwise we risk doing 2 events.
			if (e.deviceSessionId && rat.system.has.xboxLE)
			{
				var ourController = rat.input.getControllerInfo(e.deviceSessionId);
				if(ourController)
					rat.input.handleGamepadUpEvent(ratEvent, ourController);
			}
		}
		
		rat.input.handlePreventDefault(ratEvent, handled);
	};
	
	//	context menu (e.g. right-click) event handling
	rat.input.onContextMenu = function (e)
	{
		//	sad, but if we're targetting kong, need to try to maintain focus on every click.
		//if (rat.system.kong)
		//	rat.system.kong.focus();
		
		//	TODO: figure out how to trigger context menu on long-press?  Is that a whole separate thing?
		//var isTouch = e.pointerType === "touch" || e.pointerType === 0x00000002 || e.pointerType === 0x00000003;
		var isTouch = false;
		var ratEvent = rat.input.mouseToRatEvent(e, 'contextmenu', isTouch);
		rat.input.handlePreventDefault(ratEvent, ratEvent.handled);
	};

	// given a sessionId on a 'keyboard' device, find which controller the sessionID belongs to and then get our rat representation of that controllers inputs
	/** @suppress {checkTypes} */
	rat.input.getControllerInfo = function (sessionId)
	{
		var controller;
		if(rat.system.has.xbox)
		{				// I believe that this only gets called via XBO anyways, but this is precautionary just in case
			controller = rat.input.getXboxControllerInfo(sessionId);
		}
		else if(rat.system.has.xboxLE)
		{
			
			// when in an LE we dont get user specific controllers, so make a controller up
			controller = { id: 0xDEADBEEF + sessionId, user: {} };
		}

		if(!controller)
			return null;

		//	find associated controller we're tracking.
		var ourControllerIndex = -1;
		for(var ecIndex = 0; ecIndex < rat.input.controllers.length; ecIndex++)
		{
			if(rat.input.controllers[ecIndex].id === controller.id)
			{
				ourControllerIndex = ecIndex;
				break;
			}
		}
		if(ourControllerIndex < 0)	//	not found - add to list
		{
			// Xbox LE specific code, probably want to rename it so it is less specific?
			var newController = rat.input.buildRatControllerObject("xle", { type: 'gamepad', id: controller.id, user: controller.user, rawButtons: 0, newButtons: 0, lastButtons: 0 });
			rat.input.controllers.push(newController);
			ourControllerIndex = rat.input.controllers.length - 1;
		}
		var ourController = rat.input.controllers[ourControllerIndex];

		return ourController;
	};

	// when continuing a series of gameloads we may want to force the controllers to be the same IDs and indicies they were the last time we loaded the game
	// making a version specific to XBO LE's for now
	// TODO: Fix to work with all game types
	rat.input.setControllerInfoByIndex = function (controllerId, index)
	{
		if(rat.system.has.xboxLE)
		{
			if(typeof controllerId === typeof "")
				controllerId = parseInt(controllerId);
			var newController = rat.input.buildRatControllerObject("xle", { type: 'gamepad', id: controllerId, user: {}, rawButtons: 0, newButtons: 0, lastButtons: 0 });
			rat.input.controllers[index] = newController;
		}
	};
	
	// given a sessionId on a 'keyboard' device, find which controller the sessionID belongs to and then get our rat representation of that controllers inputs
	rat.input.clearAllControllerButtons = function ()
	{
		//rat.input.controllers.push({ type: 'gamepad', id: controller.id, user: controller.user, rawButtons: 0, newButtons: 0, lastButtons: 0 });
		var controller;
		for(var ecIndex = 0; ecIndex < rat.input.controllers.length; ecIndex++)
		{
			controller = rat.input.controllers[ecIndex];
			controller.rawButtons = 0;
			controller.newButtons = 0;
			controller.lastButtons = 0;
			controller.repeatTimer.fullReset();
		}
	};

	// On devices which do not set a position for touch events, copy over the touch position.
	rat.input.setTouchPositionOnEvent = function (touch, e)
	{
		// TODO: See how this works on Windows 8
		var newE = rat.utils.copyObject(e);
		//	if there's ever a specific pageX and pageY for the individual touch, use that.
		//	This is important for events with multiple touchChanges, in which case we must get
		//	the unique position values from there!
		//	If there's no such value, leave the pageX pageY values we had.
		if (touch.pageX !== void 0 || touch.pageY !== void 0)
		//if(!e.clientX && e.pageX === 0 && e.pageY === 0 && (touch.pageX !== 0 || touch.pageY !== 0))
		{
			newE.pageX = touch.pageX;
			newE.pageY = touch.pageY;
		}
		if (touch.identifier !== void 0)
			newE.pointerId = touch.identifier;
		else if (e.pointerId !== void 0)
			newE.pointerId = e.pointerId;
		return newE;
	};

	/* jshint -W082 */ //	Allow this function in a conditional
	function handleTouch(e)
	{
		var transEvent = 'mousedown';

		if (e.type === 'touchmove') transEvent = 'mousemove';
		else if (e.type === 'touchstart') transEvent = 'mousedown';
		else if (e.type === 'touchend') transEvent = 'mouseup';
		
		//var line = "" + e.type + " frame " + rat.graphics.frameIndex + ": ";
		var handled = false;
		for (var i = 0; i !== e.changedTouches.length; ++i)
		{
			var touch = e.changedTouches[i];
			var newE = rat.input.setTouchPositionOnEvent(touch, e);
			var ratEvent = rat.input.mouseToRatEvent(newE, transEvent, true);
			handled = handled || ratEvent.handled;	//	did *anyone* handle this?
			//line += "   " + touch.identifier + "(" + (touch.pageX|0) + "," + (touch.pageY|0) + ")";
		}
		var fakeRatEvent = {sysEvent: e};
		rat.input.handlePreventDefault(fakeRatEvent, handled);
		//console.log(line);
	}

	//	automatically hook up a bunch of standard UI handling functions to events.
	rat.input.autoHandleEvents = function ()
	{
		//	keys
		rat.addOSKeyEventListener(window, 'keydown', rat.input.onKeyDown, false);
		rat.addOSKeyEventListener(window, 'keyup', rat.input.onKeyUp, false);
		rat.addOSKeyEventListener(window, 'keypress', rat.input.onKeyPress, false);
		
		//	handle pointer/mouse events, including multi-touch
		
		//	This solution is extensively tested now in Chrome, IE10+, and Windows 8 Host.
		//	Don't rewrite this without retesting those!
		
		//	If the navigator says it's going to give us MSPointer events, fine, listen to those.
		//	Those will come for mouse and touch and pen.
		//	This is the case for IE10+ and for Windows 8 host
		//	TODO:  IE11 changes these to "pointermove", etc., and threatens to take the old names away.
		if (navigator.msPointerEnabled || typeof (Windows) !== 'undefined')
		{
			//console.log("listening for MS Pointer");
			
			// MS specific pointer stuff
			rat.addOSEventListener(window, 'MSPointerMove', rat.input.onMouseMove, false);
			rat.addOSEventListener(window, 'MSPointerDown', rat.input.onMouseDown, false);
			rat.addOSEventListener(window, 'MSPointerUp', rat.input.onMouseUp, false);
		}
		else	//	otherwise, listen explicitly for mouse events, and touch separately
		{
			//console.log("listening for mouse");
			
			//	listen for mouse
			rat.addOSEventListener(window, 'mousemove', rat.input.onMouseMove, false);
			rat.addOSEventListener(window, 'mousedown', rat.input.onMouseDown, false);
			rat.addOSEventListener(window, 'mouseup', rat.input.onMouseUp, false);
			
			rat.addOSEventListener(window, 'contextmenu', rat.input.onContextMenu, false);
		
			//	listen for touch
			
			//	Under wraith, document.body does not exist.  In addition, we don't need the touch events
			//	As wraith will fire them as mousemoves.
			if (document.body)	
			{
				//console.log("listening for touches");
				
				document.body.addEventListener('touchmove', handleTouch, false);
				document.body.addEventListener('touchstart', handleTouch, false);
				document.body.addEventListener('touchend', handleTouch, false);
				
				/*	OLD duplicated code which was hard to work with.
					remove when the above has been tested.
				document.body.addEventListener('touchmove', function (e)
				{
					e.preventDefault();
					var line = "tm frame " + rat.graphics.frameIndex + ": ";
					for (var i = 0; i !== e.changedTouches.length; ++i)
					{
						var touch = e.changedTouches[i];
						//	just pass on to mouse handler
						e = rat.input.setTouchPositionOnEvent(touch, e);
						var ratEvent = rat.input.mouseToRatEvent(e, 'mousemove', true);
						
						line += "   " + touch.identifier + "(" + (touch.pageX|0) + "," + (touch.pageY|0) + ")";
					}
					console.log(line);
				}, false);
				document.body.addEventListener('touchstart', function (e)
				{
					e.preventDefault();
					var line = "ts frame " + rat.graphics.frameIndex + ": ";
					for (var i = 0; i !== e.changedTouches.length; ++i)
					{
						var touch = e.changedTouches[i];
						e = rat.input.setTouchPositionOnEvent(touch, e);
						var ratEvent = rat.input.mouseToRatEvent(e, 'mousedown', true);
						
						line += "   " + touch.identifier + "(" + (touch.pageX|0) + "," + (touch.pageY|0) + ")";
					}
					console.log(line);
				}, false);
				document.body.addEventListener('touchend', function (e)
				{
					e.preventDefault();
					var line = "te frame " + rat.graphics.frameIndex + ": ";
					for (var i = 0; i !== e.changedTouches.length; ++i)
					{
						var touch = e.changedTouches[i];
						e = rat.input.setTouchPositionOnEvent(touch, e);
						var ratEvent = rat.input.mouseToRatEvent(e, 'mouseup', true);
						
						line += "   " + touch.identifier + "(" + (touch.pageX|0) + "," + (touch.pageY|0) + ")";
					}
					console.log(line);
				}, false);
				*/
			}
		}
		
		/*
		//	suppress drag?  Not working...
		// do nothing in the event handler except canceling the event
		rat.graphics.canvas.ondragstart = function(e) {
			if (e && e.preventDefault) { e.preventDefault(); }
			if (e && e.stopPropagation) { e.stopPropagation(); }
			return false;
		}

		// do nothing in the event handler except canceling the event
		rat.graphics.canvas.onselectstart = function(e) {
			if (e && e.preventDefault) { e.preventDefault(); }
			if (e && e.stopPropagation) { e.stopPropagation(); }
			return false;
		}
		*/

		//	mouse wheel
		
		//	firefox support (see https://developer.mozilla.org/en-US/docs/Web/Events/wheel)
		if (rat.system.has.firefoxBrowser)
		{
			//rat.addOSEventListener(window, 'DOMMouseScroll', rat.input.onMouseWheel, false);
			rat.addOSEventListener(window, 'wheel', rat.input.onMouseWheel, false);
		} else {
			rat.addOSEventListener(window, 'mousewheel', rat.input.onMouseWheel, false);
		}
	};

	rat.input.translateGamePadKeys = void 0;

	/**
	 * return ui event if successful translation.
	 * otherwise, return null
	 * @suppress {missingProperties} - This is needed to avoid warnings baout k.* variables
	 */
	//	util to translate key to ui event
	function translateKeyToUI(which)
	{
		
		//	support any custom translations that were set up.
		//	Do these first so an app can completely override default stuff if needed.
		//	TODO:  Hmm... these are just keyboard.  So, either name this appropriately,
		//		or add a "type" field to the structure below (maybe assume keyboard if not specified)
		/*
		How to use this:  for now, just set it directly in your app.  Something like this:
			rat.input.customUIEventTranslations = [
				{which: rat.keys.w, result: 'up'},
				{which: rat.keys.a, result: 'left'},
				{which: rat.keys.s, result: 'down'},
				{which: rat.keys.d, result: 'right'},
				{which: rat.keys.space, result: 'enter'},
			];
		*/
		if (rat.input.customUIEventTranslations)
		{
			for (var i = 0; i < rat.input.customUIEventTranslations.length; i++)
			{
				var trans = rat.input.customUIEventTranslations[i];
				if (which === trans.which)
					return trans.result;
			}
		}

		//	TODO: rename these to rat.input.uiLeft or something...
		if(which === rat.keys.leftArrow) return 'left';
		if(which === rat.keys.upArrow) return 'up';
		if(which === rat.keys.rightArrow) return 'right';
		if(which === rat.keys.downArrow) return 'down';
		if(which === rat.keys.enter) return 'enter';
		if(which === rat.keys.esc || which === rat.keys.backspace) return 'back';
		if (which === rat.keys.leftSys || which === rat.keys.rightSys) return 'menu';
		if (which === rat.keys.selectKey) return 'view';

		//	I prefer "enter" to "select" because select sometimes means "highlight".  "act" or "press" or something would also be OK.

		if(rat.system.has.xbox && rat.input.translateGamePadKeys)	//	only translate these if we were asked to
		{
			var k = window.WinJS.Utilities.Key;
			if(which === k.gamepadDPadLeft || which === k.gamepadLeftThumbstickLeft) return 'left';
			if(which === k.gamepadDPadUp || which === k.gamepadLeftThumbstickUp) return 'up';
			if(which === k.gamepadDPadRight || which === k.gamepadLeftThumbstickRight) return 'right';
			if(which === k.gamepadDPadDown || which === k.gamepadLeftThumbstickDown) return 'down';
			if(which === k.gamepadA) return 'enter';
			if(which === k.gamepadB) return 'back';
		}
		
		return null;
	}
	//	util to translate controller button to ui event
	function translateButtonToUI(which)
	{
		if(which === rat.input.BUTTON_LEFT) return 'left';
		if(which === rat.input.BUTTON_UP) return 'up';
		if(which === rat.input.BUTTON_RIGHT) return 'right';
		if(which === rat.input.BUTTON_DOWN) return 'down';
		if(which === rat.input.BUTTON_A) return 'enter';
		if(which === rat.input.BUTTON_B) return 'back';
		if(which === rat.input.BUTTON_START) return 'menu';
		if(which === rat.input.BUTTON_SELECT) return 'view';
		//	I prefer "enter" to "select" because select sometimes means "highlight".  "act" or "press" or something would also be OK.

		return null;
	}

	function translateVoiceToUI(which)
	{
		if (which === rat.voice.commands.Back)
			return 'back';
		else if (which === rat.voice.commands.Menu)
			return 'menu';
		else if (which === rat.voice.commands.view)
			return 'view';
		else
			return null;
	}
	
	//	possibly translate this event to a ui event.
	rat.input.translateToUIEvent = function (ratEvent)
	{
		var uiEventCode = 0;
		if (ratEvent.eventType === 'keydown')
		{
			uiEventCode = translateKeyToUI(ratEvent.which);
			if (uiEventCode)
				rat.input.lastUIInputType = 'keyboard';
		}
		//	In the case of button translation, don't translate fake controller inputs,
		//	since we presumably already handled the keyboard event effectively!
		else if (ratEvent.eventType === 'buttondown' && ratEvent.controllerID !== "GAMEPAD_KEYFAKE")
		{
			uiEventCode = translateButtonToUI(ratEvent.which);
			if (uiEventCode)
				rat.input.lastUIInputType = 'controller';
		}
		else if (ratEvent.eventType === 'voice')
		{
			uiEventCode = translateVoiceToUI(ratEvent.which);
			if (uiEventCode)
				rat.input.lastUIInputType = 'voice';
		}
		if (uiEventCode) {
			return new rat.input.Event(ratEvent.sysEvent, {translatedFrom: ratEvent, type: 'ui', which: uiEventCode});
		}
		return null;
	};

	//
	//	Get the "direction" to "go" based on the stick and dpad values
	rat.input.getControllerDirection = function( controller )
	{
		var pos = {
			x: 0,
			y: 0
		};
		
		if( controller )
		{
			if( controller.rawButtons & rat.input.BUTTON_LEFT )
				pos.x -= 1;
			else if( controller.rawButtons & rat.input.BUTTON_RIGHT )
				pos.x += 1;
			else
				pos.x += controller.leftStick.x;
			if( controller.rawButtons & rat.input.BUTTON_UP )
				pos.y -= 1;
			else if( controller.rawButtons & rat.input.BUTTON_DOWN )
				pos.y += 1;
			else
				pos.y += controller.leftStick.y;
		}
		return pos;
	};
	
	//
	//	Given a key code, translate to direction vector
	//	if key is NOT a direction, return null,
	//	so it's also easy to just use this to test whether it's an arrow key.
	rat.input.keyToDirection = function (keyCode)
	{
		if (keyCode === rat.keys.leftArrow)
			return {x:-1, y:0};
		else if (keyCode === rat.keys.rightArrow)
			return {x:1, y:0};
		else if (keyCode === rat.keys.upArrow)
			return {x:0, y:-1};
		else if (keyCode === rat.keys.downArrow)
			return {x:0, y:1};
		return null;
	};

	/**
	 * Helper function to detect if a button is currently being pressed.
	 * Based on https://developer.mozilla.org/en-US/docs/Web/Guide/API/Gamepad#Using_button_information
	 * @param {?} button In the current web Gamepad API as of Jun 14, 2014 12:26:10 AM, it is an object with the properties pressed and value.
	 * 			  It used to be a number value, so the type check is for browser compatability.
	 * @return {number}
	 */
	rat.input.getButtonValue = function(button)
	{
		if(typeof(button) === "object")
			return button.value;
		return button;
	};

	/**
	 * Build a rat controller object from a system controller object
	 * @param {string} fromSystem - like "xbox" or "gamepadAPI"
	 * @param {?} obj system controller object
	 * @return {rat.input.Controller}
	 * @suppress {missingProperties}
	 */
	rat.input.buildRatControllerObject = function (fromSystem, obj)
	{
		var rInput = rat.input;

		// Rat controller object format
		//{
		//	id: id unique to this controller,
		//	index: index of this controller.  Unique to the currently active controllers
		//	connected: {boolean} is this controller connected
		//	timestamp: obj.timestamp, Last updated when
		//	rawButtons: 0, Flagset of rat button flags for what button is currently down
		//	leftStick: {x:0, y:0}, raw values of the left stick
		//	rightStick: {x:0, y:0}, raw values of the right stick
		//	leftTrigger: 0, raw value of the left trigger
		//	rightTrigger: 0, raw value of the right trigger
		//  newButtons: built by rat after the conversion
		//  lastButtons: built by rat after the conversion
		//}
		var mapping;
		var rButtons = rat.input;
		var ratObj;
		switch(fromSystem)
		{
			// Mapping from gamepadAPI
			case "gamepadAPI":
				var axes = obj.axes;
				var buttons = obj.buttons;

				//	Build a unified value array where the axes are -1 -> -len
				var full = buttons.concat();
				for(var axesIndex = 0; axesIndex < axes.length; axesIndex++)
				{
					full[-(axesIndex + 1)] = axes[axesIndex];
				}

				// Axis/button mapping came from here
				// http://www.html5rocks.com/en/tutorials/doodles/gamepad/
				mapping = rInput.GAMEPAD_CONTROLLER_MAPPING;
				//	if obj.connected is undefined, assume it to be true.
				if(obj.connected === void 0)
					obj.connected = true;
				ratObj = new rInput.Controller(obj, obj.id, obj.index, obj.connected, obj.timestamp);
				ratObj.rawButtons =
					(rat.input.getButtonValue(full[mapping.BUTTON_A]) ? rButtons.BUTTON_A : 0) |
					(rat.input.getButtonValue(full[mapping.BUTTON_B]) ? rButtons.BUTTON_B : 0) |
					(rat.input.getButtonValue(full[mapping.BUTTON_C]) ? rButtons.BUTTON_C : 0) |
					(rat.input.getButtonValue(full[mapping.BUTTON_D]) ? rButtons.BUTTON_D : 0) |
					(rat.input.getButtonValue(full[mapping.BUTTON_LB]) ? rButtons.BUTTON_LB : 0) |
					(rat.input.getButtonValue(full[mapping.BUTTON_RB]) ? rButtons.BUTTON_RB : 0) |
					//(rat.input.getButtonValue(full[mapping.BUTTON_LT]) ? rButtons.BUTTON_LT : 0) | // We let rat take care of these
					//(rat.input.getButtonValue(full[mapping.BUTTON_RT]) ? rButtons.BUTTON_RT : 0) | // We let rat take care of these
					(rat.input.getButtonValue(full[mapping.BUTTON_SELECT]) ? rButtons.BUTTON_SELECT : 0) |
					(rat.input.getButtonValue(full[mapping.BUTTON_START]) ? rButtons.BUTTON_START : 0) |
					(rat.input.getButtonValue(full[mapping.BUTTON_LEFT_STICK]) ? rButtons.BUTTON_LEFT_STICK : 0) |
					(rat.input.getButtonValue(full[mapping.BUTTON_RIGHT_STICK]) ? rButtons.BUTTON_RIGHT_STICK : 0) |
					(rat.input.getButtonValue(full[mapping.BUTTON_DPAD_UP]) ? rButtons.BUTTON_DPAD_UP : 0) |
					(rat.input.getButtonValue(full[mapping.BUTTON_DPAD_DOWN]) ? rButtons.BUTTON_DPAD_DOWN : 0) |
					(rat.input.getButtonValue(full[mapping.BUTTON_DPAD_LEFT]) ? rButtons.BUTTON_DPAD_LEFT : 0) |
					(rat.input.getButtonValue(full[mapping.BUTTON_DPAD_RIGHT]) ? rButtons.BUTTON_DPAD_RIGHT : 0);
				ratObj.leftStick.x = full[mapping.leftStick.x] || 0;
				ratObj.leftStick.y = full[mapping.leftStick.y] || 0;
				ratObj.rightStick.x = full[mapping.rightStick.x] || 0;
				ratObj.rightStick.y = full[mapping.rightStick.y] || 0;
				ratObj.leftTrigger = full[mapping.leftTrigger] || 0;
				ratObj.rightTrigger = full[mapping.rightTrigger] || 0;
				return ratObj;
				
			case "xle":
				return rat.input.buildXboxLEControllerObject(fromSystem, obj);
				
			case "xbox":
				return rat.input.buildXboxControllerObject(fromSystem, obj);
				
			default:
				return new rInput.Controller(obj, 0, -1, false, 0);
		}
	};
	
	/**
	 * Build a fake controller object,
	 *	matching the chrome (webkit) gamePadAPI format,
	 *	based on keyboard state.
	 *	Very useful for developing projects that depend on controller inputs
	 *	(like lua games) when you don't have a controller connected,
	 *	or are tired of dealing with how flaky chrome's controller support is.
	 */
	rat.input.buildGamepadKeyFake = function ()
	{
		var fake = {
			id: 'GAMEPAD_KEYFAKE',
			index: 4,
			//mapping
			//connected
			//timestamp
			axes: [],
			buttons: [
				//	this list matches the standard gamePadAPI mapping,
				//	which is what we're going to fake here.
				rat.input.keyboard.isKeyDown(rat.keys.a) | rat.input.keyboard.isKeyDown(rat.keys.enter),
				rat.input.keyboard.isKeyDown(rat.keys.b) | rat.input.keyboard.isKeyDown(rat.keys.esc),
				rat.input.keyboard.isKeyDown(rat.keys.x),
				rat.input.keyboard.isKeyDown(rat.keys.y),
				rat.input.keyboard.isKeyDown(rat.keys.leftBracket),	//	lb
				rat.input.keyboard.isKeyDown(rat.keys.rightBracket),	//	rb
				rat.input.keyboard.isKeyDown(rat.keys.l) | rat.input.keyboard.isKeyDown(rat.keys.semicolon),	//	lt
				rat.input.keyboard.isKeyDown(rat.keys.r) | rat.input.keyboard.isKeyDown(rat.keys.singleQuote),	//	rt
				0,0,	//	sel,start
				0,0,	//	stick button, stick button
				//	ijkl map to dpad
				rat.input.keyboard.isKeyDown(rat.keys.i),	//	dpad
				rat.input.keyboard.isKeyDown(rat.keys.k),
				rat.input.keyboard.isKeyDown(rat.keys.j),
				rat.input.keyboard.isKeyDown(rat.keys.l)
			],
		};
		
		//	arrow keys map to analog stick
		if (rat.input.keyboard.isKeyDown(rat.keys.upArrow))
			fake.axes[1] = -1;
		else if (rat.input.keyboard.isKeyDown(rat.keys.downArrow))
			fake.axes[1] = 1;
		if (rat.input.keyboard.isKeyDown(rat.keys.leftArrow))
			fake.axes[0] = -1;
		else if (rat.input.keyboard.isKeyDown(rat.keys.rightArrow))
			fake.axes[0] = 1;
		
		return fake;
	};

	/**
	 * event called when a controller is added
	 * @param {?} e
	 * @return {?rat.input.Controller}
	 * @suppress {missingProperties}
	 */
	rat.input.onSysControllerAdded = function (e)
	{
		var rInput = rat.input;
		if(rat.system.has.xbox)
		{
			return null;
		}
			/**  GamepadAPI
 */
		else
		{
			var sysGP = e.gamepad;
			//	Get the rat version of the controller
			var ratGP = rInput.buildRatControllerObject("gamepadAPI", sysGP);

			//	Handle this controller being added
			return rInput.onControllerChange(rInput.ControllerChangeType.ADDED, ratGP);
		}
	};

	/**
	 * event called when a controller is removed
	 * @param {?} e
	 * @return {?rat.input.Controller}
	 * @suppress {missingProperties}
	 */
	rat.input.onSysControllerRemoved = function (e)
	{
		var rInput = rat.input;
		if(rat.system.has.xbox)
		{
			return null;
		}
			/**  GamepadAPI
 */
		else
		{
			var sysGP = e.gamepad;
			//	Get the rat version of the controller
			var ratGP = rInput.buildRatControllerObject("gamepadAPI", sysGP);

			//	Handle it being removed
			return rInput.onControllerChange(rInput.ControllerChangeType.REMOVED, ratGP);
		}
	};

	/**
	 * Called when a controller is added, remove, or generally updated
	 * @param {number} reason Why was this method called
	 * @param {!rat.input.Controller} controller The controller data
	 * @return {!rat.input.Controller}
	 */
	rat.input.onControllerChange = function (reason, controller)
	{
		//	Find this controller's index if it is in the controllers list
		var rInput = rat.input;
		var foundAtIndex = -1;
		var list = rInput.controllers;
		for(var searchIndex = list.length - 1; searchIndex >= 0; --searchIndex)
		{
			if(list[searchIndex].id === controller.id)
			{
				foundAtIndex = searchIndex;
				break;
			}
		}

		//	Some general state handling
		if(reason === rInput.ControllerChangeType.REMOVED)
		{
			//	If we didn't find it, then we have nothing to do
			if(foundAtIndex === -1)
				return controller;

			//	The controller is not active in any way.
			controller.rawButtons = 0;
			controller.leftStick.x = 0;
			controller.leftStick.y = 0;
			controller.rightStick.x = 0;
			controller.rightStick.y = 0;
			controller.connected = false;
			controller.leftTrigger = 0;
			controller.rightTrigger = 0;
			controller.repeatTimer.fullReset();
		}
		else if(reason === rInput.ControllerChangeType.ADDED)
		{
			controller.connected = true;
		}

		//	Get the lastButtons if we found it, and make sure that this object is in the list
		if(foundAtIndex === -1)
		{
			controller.lastButtons = 0;
			foundAtIndex = list.length;
			list.push(controller);
			//rat.console.log("Controller " + controller.id + " added");
			//rat.console.log("   FullInfo");
			//rat.console.log(JSON.stringify(controller));
			//rat.console.log("\n");
		}
		else
		{
			controller.repeatTimer = list[foundAtIndex].repeatTimer;
			controller.lastButtons = list[foundAtIndex].lastButtons;
			list[foundAtIndex] = controller;
		}

		return controller;
	};

	/**
	 * Update the users controllers, if any
	 * @param {number} dt Delta time
	 */
	rat.input.updateControllers = function (dt)
	{
		var rInput = rat.input;
		var list = rInput.controllers;
		var thresholds = rInput.thresholds;
		
		if (rInput.controllers.getSystemControllers)
		{
			//	If we are a pollingForced system for controllers, find any changes to what we have and call
			//	the correct onController* method
			var gamepads = rInput.controllers.getSystemControllers();
			var index;

			var found;
			var searchIndex;
			var newController;
			var changeType;
			if (rInput.controllers.pollingForced)
			{
				//	gamepad API polling.
				if (rat.system.has.gamepadAPI || fakeGamepadAPI)// NOTE: The xbox does set this.
				{
					//	Process the currently list of gamepads from the system
					var system = "gamepadAPI";
					if (rat.system.has.xbox)
						system = "xbox";
					for (index = gamepads.length - 1; index >= 0; --index)
					{
						if (!gamepads[index])
							continue;

						//	Get the RAT version of the controller object
						newController = rInput.buildRatControllerObject(system, gamepads[index]);

						//	Find the controller
						for (searchIndex = list.length - 1; searchIndex >= 0; --searchIndex)
						{
							if (list[searchIndex].id === newController.id)
							{
								found = true;
								break;
							}
						}

						//	If it is not connected now, it has been removed
						if (!newController.connected)
							changeType = rInput.ControllerChangeType.REMOVED;
						else if (!found)
							changeType = rInput.ControllerChangeType.ADDED;
						else
							changeType = rInput.ControllerChangeType.UPDATED;
						newController = rInput.onControllerChange(changeType, newController);
					}
				}
			}
		}

		//	Rat level event detection and dispatch
		var controller;
		for(index = list.length - 1; index >= 0; --index)
		{
			controller = list[index];

			//	Translate left stick values to direction
			if (controller.leftStick.y <= -thresholds.PRESSED)
				controller.rawButtons |= rInput.BUTTON_LSTICK_UP;
			if (controller.leftStick.y >= thresholds.PRESSED)
				controller.rawButtons |= rInput.BUTTON_LSTICK_DOWN;
			if (controller.leftStick.x <= -thresholds.PRESSED)
				controller.rawButtons |= rInput.BUTTON_LSTICK_LEFT;
			if (controller.leftStick.x >= thresholds.PRESSED)
				controller.rawButtons |= rInput.BUTTON_LSTICK_RIGHT;
				
			//	Translate right stick values to direction
			if (controller.rightStick.y <= -thresholds.PRESSED)
				controller.rawButtons |= rInput.BUTTON_RSTICK_UP;
			if (controller.rightStick.y >= thresholds.PRESSED)
				controller.rawButtons |= rInput.BUTTON_RSTICK_DOWN;
			if (controller.rightStick.x <= -thresholds.PRESSED)
				controller.rawButtons |= rInput.BUTTON_RSTICK_LEFT;
			if (controller.rightStick.x >= thresholds.PRESSED)
				controller.rawButtons |= rInput.BUTTON_RSTICK_RIGHT;
				
			//	Left stick and dpad values map to BUTTON_<DIRECTION> to make input dectection easier
			//	NOTE that the right stick is NOT Part of this because we often have special behavior for the right stick
			if ((controller.rawButtons & (rInput.BUTTON_DPAD_UP | rInput.BUTTON_LSTICK_UP)) !== 0)
				controller.rawButtons |= rInput.BUTTON_UP;
			if ((controller.rawButtons & (rInput.BUTTON_DPAD_DOWN | rInput.BUTTON_LSTICK_DOWN)) !== 0)
				controller.rawButtons |= rInput.BUTTON_DOWN;
			if ((controller.rawButtons & (rInput.BUTTON_DPAD_LEFT | rInput.BUTTON_LSTICK_LEFT)) !== 0)
				controller.rawButtons |= rInput.BUTTON_LEFT;
			if ((controller.rawButtons & (rInput.BUTTON_DPAD_RIGHT | rInput.BUTTON_LSTICK_RIGHT)) !== 0)
				controller.rawButtons |= rInput.BUTTON_RIGHT;
			
			//	And the triggers
			if(rat.input.getButtonValue(controller.leftTrigger) >= thresholds.PRESSED)
				controller.rawButtons |= rInput.BUTTON_LT;
			if(rat.input.getButtonValue(controller.rightTrigger) >= thresholds.PRESSED)
				controller.rawButtons |= rInput.BUTTON_RT;

			// What buttons are newly pressed.
			controller.newButtons = controller.rawButtons & ~controller.lastButtons;

			//	Fire events
			rInput.checkAndDispatchControllerEvents(controller, dt);

			//	Remember what the buttons where last frame.
			controller.lastButtons = controller.rawButtons;

			//	If it isn't connected, remove it.
			if (!controller.connected)
			{
				list.splice(index, 1);
				rat.console.log("Controller " + controller.id + " removed");
			}
		}
	};

	rat.input.setOneAllowedController = function (id)
	{
		if (id === 'keyboard' || id === 'mouse')
			return;
		rat.console.log("Allowing only controller " + id);
		rat.input.allowedControllers = [id];
	};
	rat.input.allowAllControllers = function ()
	{
		rat.input.allowedControllers = [];
	};

	rat.input.setAllowRepeatEvents = function (allowed)
	{
		if (allowed === true || allowed === false)
		{
			rat.input.allowRepeatEvents.buttons = allowed;
			rat.input.allowRepeatEvents.directions = allowed;
		}
		else
		{
			if (allowed.buttons !== void 0)
				rat.input.allowRepeatEvents.buttons = !!allowed.buttons;
			if (allowed.directions !== void 0)
				rat.input.allowRepeatEvents.directions = !!allowed.directions;
		}
	};

	/**
	 * A basic repeat timer for controller
	 * @constructor
	 */
	var RepeatTimer = function (rate, delay)
	{
		this.defaults = {
			rate: rate,
			delay: delay
		};
		this.buttons = 0;
		this.directions = 0;
		this.fullReset();
	};
	RepeatTimer.prototype.fullReset = function ()
	{
		this.buttons = this.defaults.delay.buttons;
		this.directions = this.defaults.delay.directions;
	};
	RepeatTimer.prototype.repeatReset = function (ops)
	{
		if (ops === void 0)
			ops = { buttons: true, directions: true };
		if (ops.buttons)
			this.buttons = this.defaults.rate.buttons;
		if (ops.directions)
			this.directions = this.defaults.rate.directions;
	};
	RepeatTimer.prototype.elapsed = function (dt)
	{
		this.buttons -= dt;
		this.directions -= dt;
		if (this.buttons < 0)
			this.buttons = 0;
		if (this.directions < 0)
			this.directions = 0;
	};

	/**
	 * The rat controller type
	 * @constructor
	 * @param {?} id unique ID assigned to each controller
	 * @param {number} index of the controller in the system.  Unique for currently connected controllers
	 * @param {boolean} connected is it currently connected 
	 * @param {?} timestamp
	 */
	rat.input.Controller = function (rawData, id, index, connected, timestamp)
	{
		this.rawData = rawData || {};
		this.id = id;
		this.index = index;
		this.connected = connected;
		this.timestamp = timestamp;
		this.rawButtons = 0;
		this.leftStick = {
			x: 0,
			y: 0
		};
		this.rightStick = {
			x: 0,
			y: 0
		};
		this.leftTrigger = 0;
		this.rightTrigger = 0;
		this.newButtons = 0;
		this.lastButtons = 0;
		this.repeatTimer = new RepeatTimer(rat.input.Controller.repeatRate, rat.input.Controller.repeatDelay);
	};

	/**  How fast should event repeats happen
 */
	rat.input.Controller.repeatRate = {
		buttons: 0.2,	/**  For buttons
 */
		directions: 0.2	/**  For directions
 */
	};
	/**  What is the delay before we start to repeat
 */
	rat.input.Controller.repeatDelay = {
		buttons: 0.5,	/**  For buttons
 */
		directions: 0.5	/**  For directions
 */
	};

	/**
	 * Thresholds for analog values
	 * @enum {number}
	 */
	rat.input.thresholds = {
		LOW: 0.2,
		NORM: 0.5,
		HIGH: 0.8,
		PRESSED: 0.7, // We use this to know if a stick is pushing in a direction
	};


	//	a bunch of more convenient names, especially for backward compatibility.

	rat.getRealMousePosition = rat.input.getRealMousePosition;
	//rat.dispatchMouseMove = rat.input.dispatchMouseMove;
	//rat.dispatchMouseDown = rat.input.dispatchMouseDown;
	//rat.dispatchMouseUp = rat.input.dispatchMouseUp;
	rat.standardizeKeyEvent = rat.input.standardizeKeyEvent;
	rat.charCodeFromEvent = rat.input.charCodeFromEvent;

	rat.autoHandleEvents = rat.input.autoHandleEvents;


	//	common key definitions so we can stop using numbers everywhere
	//	TODO: move this to rat.keyboard?
	rat.keys = {
		leftArrow: 37,
		upArrow: 38,
		rightArrow: 39,
		downArrow: 40,

		enter: 13,
		esc: 27,
		backspace: 8,
		del: 46,
		space: 32,
		home: 36,
		end: 35,
		' ': 32,
		tab: 9,
		pageUp: 33,
		pageDown: 34,
		period: 190,
		semicolon : 186,
		singleQuote : 222,
		comma : 188,
		shift: 16,
		
		'0' : 48,
		'1' : 49,
		'2' : 50,
		'3' : 51,
		'4' : 52,
		'5' : 53,
		'6' : 54,
		'7' : 55,
		'8' : 56,
		'9' : 57,
		
		numPad0 : 96,
		numPad1 : 97,
		numPad2 : 98,
		numPad3 : 99,
		numPad4 : 100,
		numPad5 : 101,
		numPad6 : 102,
		numPad7 : 103,
		numPad8 : 104,
		numPad9 : 105,
		
		numPadPlus : 107,
		numPadMinus : 109,

		a: 65, b: 66, c: 67, d: 68, e: 69, f: 70, g: 71, h: 72,
		i: 73, j: 74, k: 75, l: 76, m: 77, n: 78, o: 79, p: 80,
		q: 81, r: 82, s: 83, t: 84, u: 85, v: 86, w: 87, x: 88,
		y: 89, z: 90,

		'~': 192,
		'`': 192,
		
		'-': 189,
		'=': 187,

		leftSys: 91, // Systems key (like the window key)
		rightSys: 92,

		selectKey: 93,
		
		'/' : 191,
		forwardSlash: 191,
		'\\' : 220,
		backslash : 220,
		
		leftBracket : 219,
		rightBracket: 221,

		f1: 112,
		f2: 113,
		f3: 114,
		f4: 115,
		f5: 116,
		f6: 117,
		f7: 118,
		f8: 119,
		f9: 120,
		f10: 121,
		f11: 122,
		f12: 123,
	};
	
	//	if this key code corresponds with a real number, return that, otherwise -1
	rat.input.keyCodeToNumber = function(which)
	{
		if (which >= rat.keys['0'] && which <= rat.keys['9'])
			return which - rat.keys['0'];
		if (which >= rat.keys.numPad0 && which <= rat.keys.numPad9)
			return which - rat.keys.numPad0;
			
		return -1;
	};
	
	//	Handle calling the system event's preventDefault (or not based on flags)
	//	presumably called only after everybody's had a chance to express their opinion on whether or not the default should happen.
	rat.input.handlePreventDefault = function(ratEvent, handled)
	{
		var prevent = rat.input.standardPreventDefaultOnEvents;
		
		if (handled === void 0)
		{
			//	leave default above (by default, prevent!)
		} else if (handled)
		{
			//	STOP the browser default handling
			prevent = true;
		} else {	//	false!
			//	ALLOW the browser default handling
			//	you didn't handle it, so we're allowing to flow up, regardless of what the standard is.
			prevent = false;
		}
		
		//	Regardless of what the "handled" state was, IF allowBrowserDefault has been
		//	set, respect the value.
		if (ratEvent.allowBrowserDefault !== void 0)
			prevent = !ratEvent.allowBrowserDefault;
		
		//	if prevent defaults is desired, do it.  Unless we're in qunit, in which case never prevent default.
		if (prevent && !rat.system.has.QUnit )
		{
			if( ratEvent.sysEvent && ratEvent.sysEvent.preventDefault )
			{
				ratEvent.sysEvent.preventDefault();
			}
		}
		
	};
	
	//	a hopefully sensical way to set this global
	rat.input.setStandardPreventFlag = function(prevent)
	{
		rat.input.standardPreventDefaultOnEvents = prevent;
	};
	
} );

//--------------------------------------------------------------------------------------------------
//
//	User and profile management
//	Find active users, get information about them like user name, gamer picture, list of friends, gamerscore, etc.
//
//	This is a generic API, with system specifics implemented in dependent modules (e.g. r_user_xbo.js)
//
//
/*	NOTES from Steve 2015.12.18
	r_user's getUsers() system is strange...
	It seems to be designed to reconstruct a list of users, including building a new user object for each user, each time it's called.
	So I thought maybe it was a sort of one-time setup thing, but the results aren't tracked by r_user, and it does seem to get called in other places on the fly...
	Seems not ideal at all.
	Shouldn't we have a single user list that's built once and maintained?
	I don't want to break xbox user management support, of course, so I don't want to change this stuff...
	for instance, calling setActiveUser() calls getUser() which calls getUsers() which rebuilds the list.
	All I wanted to do was set the active user...
	
	Talked to John about this...
	he agrees that the best way to do this is to TRACK the user list when it changes, rather than reconstruct it each time getUser() is called.
	TODO:  do that on various platforms.
*/

rat.modules.add( "rat.os.r_user",
[
	{ name: "rat.utils.r_messenger", processBefore: true},
	
	{ name: "rat.os.r_user_xbo", platform: "xbox" },
	{ name: "rat.os.r_user_wraith", platform: "Wraith" },
	//{ name: "rat.os.r_user_kong" }//, platform: "kong" },	//	hmm...  I want to fake it when I'm not really on kong, though...
	"rat.debug.r_console",
], 
function(rat)
{
	//rat.console.log("SLD rat.user");

	/**  Rat user object
	  * @constructor */
	var User = function (rawData, fields)
	{
		this.rawData = rawData;
		this.id = fields.id;
		this.gamerTag = fields.gamerTag;
		this.isSignedIn = fields.isSignedIn || false;
		this.userImageSource = fields.userImageSource;
		this.friendsList = fields.friendsList;
	};
	
	//	Rat user list.
	/** @constructor */
	var UserList = function ()
	{
		this.list = [];
		this.byId = {};
		this.length = 0;
	};
	UserList.prototype.add = function (rawData, fields)
	{
		var usr = new User(rawData, fields);
		this.list.push(usr);
		this.length = this.list.length;
		this.byId[usr.id] = usr;
		return usr;
	};
	UserList.prototype.at = function (index)
	{
		return this.list[index];
	};

	// Rat.user namespace
	rat.user = rat.user || {};
	rat.user.supported = rat.user.supported || false;
	rat.user.messages = new rat.Messenger();
	var messageType = {
		ActiveUserChanged: "activeUserChanged",
		SignIn: "signIn",
		SignOut: "signOut",
		InfoChange: "infoChange",
		SigninUIClosed: "signinUIClosed",
		ControllerBindingChanged: "controllerBindingChanged",
		
		FriendsInfoAvailable: "friendsInfoAvailable",
		AchievementInfoAvailable: "achievementInfoAvailable",
	};
	rat.user.messageType = messageType;
	var SigninUIOps = {
		NoGuests: "", //<DEFAULT
		AllowGuests: "allowGuests",
		AllowOnlyDistinctControllerTypes: "allowOnlyDistinctControllerTypes"
	};
	rat.user.SigninUIOps = SigninUIOps;

	var activeUser = void 0;
	
	rat.user._internal = {
		UserList: UserList,
		User: User,
		isSigninUIShowing: false
	};

	//	NULL function to get a controllerID for a given userID
	rat.user.userIDFromControllerID = function (controllerID)
	{
		if (controllerID === 'keyboard' || controllerID === 'mouse')
			return rat.user.getUsers().at(0).id;
		return 0;
	};

	// NULL function to get the controller ID tied to a user ID
	rat.user.controllerIDFromUserID = function (userID)
	{
		return 0;
	};

	// NULL function to get the list of users interacting with the local system
	rat.user.getUsers = function ()
	{
		return new UserList();
	};
	
	rat.user.requestFriendsInfo = function(user)
	{
		//	by default, there is no such service
		return;
	};

	// NULL function for showing the signin UI
	//	Signature MUST be controllerID, ops, (see rat.user.SigninOps), doneCB and return true/false if the UI was requested
	//- NOTE We do this in a function so i can provide information to the google closure compiler w/out changing the state for the entire file
	/** @suppress {checkTypes} */
	function setNULLSigninUI()
	{
		rat.user.showSigninUI = void 0;	//	This is VOID 0 so code can detect when platforms don't support this
	}
	setNULLSigninUI();

	//	Find out if the signinUI is up.
	rat.user.isSigninUIShowing = function ()
	{
		return rat.user._internal.isSigninUIShowing;
	};

	//	Get a user by the user's ID
	rat.user.getUser = function (id)
	{
		//	Find the user.
		var users = rat.user.getUsers();
		return users.byId[id];
	};

	//	Function to clear the currently set active user.
	/** @param {Object=} options */
	rat.user.clearActiveUser = function (options)
	{
		rat.user.setActiveUser(void 0, options);
	};

	// Function to set who the active user is
	/**
	 * @param {?} id 
	 * @param {Object=} options
	 */
	rat.user.setActiveUser = function (id, options)
	{
		options = options || {};

		//	Allow passing the user object
		if (id !== void 0 && id.id !== void 0)
			id = id.id;

		//	Is there a change?
		var isChange;
		if (id !== void 0)
			isChange = activeUser === void 0 || activeUser.id !== id;
		else
			isChange = activeUser !== void 0;
		if (!isChange && !options.force)
			return;

		//	Find the user.
		var old = activeUser;
		if (id !== void 0)
			activeUser = rat.user.getUser(id);
		else
			activeUser = void 0;
		rat.console.log("Active user set to:" + JSON.stringify(activeUser));
		if (isChange)
			rat.user.messages.broadcast(messageType.ActiveUserChanged, activeUser, old);
	};

	//	Get the active user object (if any)
	rat.user.getActiveUser = function ()
	{
		return activeUser;
	};

	//	Get the user ID of the active user (if any)
	rat.user.getActiveUserID = function ()
	{
		var user = rat.user.getActiveUser() || {};
		return user.id || 0;
	};

	//	Get the active user's name
	rat.user.getActiveUserName = function ()
	{
		var user = rat.user.getActiveUser() || {};
		return user.gamerTag || "";
	};
	
	//	Get the active user's image (e.g. avatar),
	//	in a simple data source or URL format,
	//	e.g. suitable for creating a rat image
	rat.user.getActiveUserImageSource = function ()
	{
		var user = rat.user.getActiveUser() || {};
		return user.userImageSource || void 0;
	};
	
	//	todo: user switched controllers?  Wraith handles this, but I don't know about anyone else.
	//	todo: user signed out
} );
//--------------------------------------------------------------------------------------------------
//
// rat color classes and utils
//

rat.modules.add("rat.graphics.r_color",
[
	{ name: "rat.graphics.r_graphics", processBefore: true },
],
function (rat)
{
	var math = rat.math; // for quick local access below

	/**
	 * @constructor
	 * @param {number=} r red (or several other options, including constructing from string or other color object)
	 * @param {number=} g green
	 * @param {number=} b blue
	 * @param {number=} a optional alpha value (assumed to be 1)
	 */
	rat.graphics.Color = function (r, g, b, a)
	{
		//	we support various forms of constructing a color,
		
		//	if nothing was passed in...
		if (typeof r === 'undefined')
		{
			this.r = 255;
			this.g = 255;
			this.b = 255;
			this.a = 1;
		//	if a style string was passed in
		} else if (typeof r === 'string')
		{
			this.copyFrom(rat.graphics.Color.makeFromStyleString(r));
		//	if an object with r,g,b properties was passed in
		} else if (r.r !== void 0)
		{
			this.r = r.r;
			this.g = r.g;
			this.b = r.b;
			if (r.a === void 0)	//	still OK to not define a explicitly, in which case it's considered 1
				this.a = 1;
			else
				this.a = r.a;
			this.applyLimits();
		}
		//	otherwise, expect individual r,g,b,a values
		else
		{
			this.r = r;
			this.g = g;
			this.b = b;
			if (a === void 0)	//	still OK to not define a explicitly, in which case it's considered 1
				this.a = 1;
			else
				this.a = a;
			this.applyLimits();
		}
	};

	/**
	 * Multiply this with another color
	 */
	rat.graphics.Color.prototype.mult = function (r, g, b, a, dest)
	{
		//	allow first argument to be a color object instead of separate "r" value
		if (r.r !== void 0)
		{
			dest = g;	//	in which case, second arg is "destination"
			a = r.a;
			b = r.b;
			g = r.g;
			r = r.r;
		}

		var r = this.r * (r / 255);
		var g = this.g * (g / 255);
		var b = this.b * (b / 255);
		var a = this.a * a;

		if (!dest)
			dest = new rat.graphics.Color(r, g, b, a);
		else
			dest.set(r, g, b, a);
		dest.applyLimits();
		return dest;
	};
	
	//	scale this color (multiply by scalar)
	rat.graphics.Color.prototype.scale = function (val)
	{
		this.r *= val;
		this.g *= val;
		this.b *= val;
		this.applyLimits();
	};

	/**
	 * Make sure that all fields of the color respect their limit (0-255, 0-1)
	 */
	rat.graphics.Color.prototype.applyLimits = function ()
	{
		// CLAMP
		if (this.r < 0) this.r = 0;
		else if (this.r > 255) this.r = 255;
			// Floor.  This only works for numbers >= 0
		else this.r = this.r | 0;

		// CLAMP
		if (this.g < 0) this.g = 0;
		else if (this.g > 255) this.g = 255;
			// Floor.  This only works for numbers >= 0
		else this.g = this.g | 0;

		// CLAMP
		if (this.b < 0) this.b = 0;
		else if (this.b > 255) this.b = 255;
			// Floor.  This only works for numbers >= 0
		else this.b = this.b | 0;

		// CLAMP
		if (this.a < 0) this.a = 0;
		else if (this.a > 1) this.a = 1;
	};

	rat.graphics.Color.prototype.toString = function ()
	{
		return "rgba(" + this.r + "," + this.g + "," + this.b + "," + this.a + ")";
	};

	rat.graphics.Color.prototype.setWhite = function ()
	{
		this.r = 255;
		this.g = 255;
		this.b = 255;
		this.a = 1;
	};

	rat.graphics.Color.prototype.setRandom = function (rng)
	{
		if (!rng)
			rng = math;
		this.r = ((rng.random() * 200) | 0) + 54;
		this.g = ((rng.random() * 200) | 0) + 54;
		this.b = ((rng.random() * 200) | 0) + 54;
		this.a = 1;
	};
	//	non-method version - just give me a random color (color object)
	rat.graphics.Color.randomColor = function (rng)
	{
		var c = new rat.graphics.Color();
		c.setRandom(rng);
		return c;
	};

	rat.graphics.Color.prototype.copy = function ()
	{
		return new rat.graphics.Color(this.r, this.g, this.b, this.a);
	};

	rat.graphics.Color.prototype.set = function (r, g, b, a)
	{
		if (a === void 0)
			a = 1;
		this.r = r;
		this.g = g;
		this.b = b;
		this.a = a;
	};

	rat.graphics.Color.prototype.copyFrom = function (c)
	{
		this.set(c.r, c.g, c.b, c.a);
	};

	rat.graphics.Color.prototype.equal = function (other)
	{
		if (this.r === other.r && this.g === other.g && this.b === other.b && this.a === other.a)
			return true;
		else
			return false;
	};

	rat.graphics.Color.prototype.distanceSq = function (other)
	{
		var dr = this.r - other.r;
		var dg = this.g - other.g;
		var db = this.b - other.b;
		return dr * dr + dg * dg + db * db;
	};
	
	rat.graphics.Color.prototype.luminance = function ()
	{
		return 0.2126 * this.r/255 + 0.7152 * this.g/255 + 0.0722 * this.b/255;
	};
	rat.graphics.Color.luminanceRGB = function (r, g, b)
	{
		return 0.2126 * r/255 + 0.7152 * g/255 + 0.0722 * b/255;
	};
	rat.graphics.Color.prototype.luminancePerceived = function ()
	{
		return 0.299 * this.r/255 + 0.587 * this.g/255 + 0.114 * this.b/255;
	};
	rat.graphics.Color.luminancePerceivedRGB = function (r, g, b)
	{
		return 0.299 * r/255 + 0.587 * g/255 + 0.114 * b/255;
	};
	rat.graphics.Color.prototype.luminancePerceived2 = function ()
	{
		return Math.sqrt(0.299 * this.r * this.r / (255*255) + 0.587 * this.g * this.g / (255*255) + 0.114 * this.b * this.b / (255*255));
	};
	
	//	using this existing color, pick the nearest color that matches this target's luminance.
	rat.graphics.Color.prototype.matchLuminanceTo = function(refColor)
	{
		var startLum = refColor.luminancePerceived();
		
		var passCount = 20;
		for (var pass = 0; pass < passCount; pass++)
		{
			var lum = this.luminancePerceived();	//	test
			var dlum = startLum - lum;
			if (dlum < 0)
			{
				this.r -= 10;
				this.g -= 10;
				this.b -= 10;
			} else {
				this.r += 10;
				this.g += 10;
				this.b += 10;
			}
			this.applyLimits();
		}
		
		/*	another attempt...
		var startLum = refColor.luminancePerceived();
		
		//	fix luminance by adjusting colors, and do that in random order.
		//	This pretty much sucks.
		//	Need real math to find closest color that matches luminance, but uses the same HUE.
		//	Need Hue Saturation Luminance, basically.  (HSP?)  Keep hue, move lum, then convert to rgb.
		//	Can HSV do this?  I don't think so!  It's not the same!
		//	maybe http://stackoverflow.com/questions/6478284/whats-the-formula-to-increase-the-luminance-in-an-image-in-a-similar-way-how-th
		//	https://en.wikipedia.org/wiki/HSL_and_HSV
		//	(look for "From luma/chroma/hue")
		//	http://colormine.org/convert/rgb-to-lab
		//	but we really just want luminance, not luma.
		//	I need a color model that uses luminance.
		
		var values = this;
		
		var LUM_R = 0.299;
		var LUM_G = 0.587;
		var LUM_B = 0.114;
		
		var passCount = 20;
		for (var pass = 0; pass < passCount; pass++)
		{
			var order = [
				{name:'r', ratio: LUM_R},
				{name:'g', ratio: LUM_G},
				{name:'b', ratio: LUM_B},
			];
			rat.utils.randomizeList(order);
		
			for (var i = 0; i < 3; i++)
			{
				var o = order[i];
				
				var oldVal = values[o.name];
				
				var newLum = startLum;
				for (var otherIndex = 0; otherIndex < 3; otherIndex++)
				{
					if (otherIndex === i)
						continue;
					newLum -= order[otherIndex].ratio * values[order[otherIndex].name]/255;
				}
				var fixed = newLum / o.ratio;
				values[o.name] = fixed * 255 / 8 + oldVal * 7/8;	//	only partway there each pass
				
				//	old:
				//var fixed = (startLum - LUM_R * newColor.r/255 - LUM_B * newColor.b/255) / LUM_G;
				//newColor.g = fixed * 255;
				this.applyLimits();
			}
		}
		
		//	check it...
		var lum = this.luminancePerceived();	//	test
		var dlum = lum - startLum;
		if (dlum < -0.1 || dlum > 0.1)
		{
			console.log("dlum = " + (lum - startLum));
		}
		*/
		
		/*
		//	Old: convert to LUV and adjusting L and convert back.
		//	Technically wrong?  Yeah, 
		var refLuv = rgb2luv(refColor.r, refColor.g, refColor.b);
		var luv = rgb2luv(this.r, this.g, this.b);
		
		luv.l = refLuv.l;
		//luv.v = refLuv.v;
		
		var rgb = luv2rgb(luv);
		this.r = rgb.r;
		this.g = rgb.g;
		this.b = rgb.b;
		this.applyLimits();
		*/
	};

	//	create a new color by interpolating between these colors
	rat.graphics.Color.interp = function (from, to, ival, dest)
	{
		var invIVal = 1 - ival;
		var r = to.r * ival + invIVal * from.r;
		var g = to.g * ival + invIVal * from.g;
		var b = to.b * ival + invIVal * from.b;
		var a = to.a * ival + invIVal * from.a;
		r = ((r < 0) ? 0 : ((r > 255) ? 255 : (r | 0)));
		g = ((g < 0) ? 0 : ((g > 255) ? 255 : (g | 0)));
		b = ((b < 0) ? 0 : ((b > 255) ? 255 : (b | 0)));
		a = ((a < 0) ? 0 : ((a > 1) ? 1 : a));
		if (dest)
		{
			dest.r = r;
			dest.g = g;
			dest.b = b;
			dest.a = a;
		}
		else
		{
			dest = new rat.graphics.Color(r, g, b, a);
		}
		return dest;
	};

	/** JHS adding a dest field to avoid new5
	  * @param {Object=} dest */
	rat.graphics.Color.prototype.randomVariance = function (variance, dest)
	{
		//c.r = clamp(color.r + math.random() * variance.r - variance.r/2, 0, 255);
		//var r = this.r + math.randomVariance(variance.r);
		//var g = this.g + math.randomVariance(variance.g);
		//var b = this.b + math.randomVariance(variance.b);
		//var a = this.a + math.randomVariance(variance.a);
		var r = this.r;
		var g = this.g;
		var b = this.b;
		var a = this.a;
		if (variance.r)
		{
			r += (variance.r * 2 * math.random() - variance.r);
			r = ((r < 0) ? 0 : ((r > 255) ? 255 : (r | 0)));
		}
		if (variance.g)
		{
			g += (variance.g * 2 * math.random() - variance.g);
			g = ((g < 0) ? 0 : ((g > 255) ? 255 : (g | 0)));
		}
		if (variance.b)
		{
			b += (variance.b * 2 * math.random() - variance.b);
			b = ((b < 0) ? 0 : ((b > 255) ? 255 : (b | 0)));
		}
		if (variance.a)
		{
			a += (variance.a * 2 * math.random() - variance.a);
			a = ((a < 0) ? 0 : ((a > 1) ? 1 : a));
		}



		if (dest)
		{
			dest.r = r;
			dest.g = g;
			dest.b = b;
			dest.a = a;
		}
		else
		{
			dest = new rat.graphics.Color(r, g, b, a);
		}
		return dest;
	};

	//	convert hsv color to rgb color
	function hsv2rgb(h, s, v)
	{
		h = (h % 1 + 1) % 1; // wrap hue
		if (s > 1)
			s = 1;
		if (v > 1)
			v = 1;

		//var i = Math.floor(h * 6),
		var i = ((h * 6)|0),
		f = h * 6 - i,
		p = v * (1 - s),
		q = v * (1 - s * f),
		t = v * (1 - s * (1 - f));

		switch (i)
		{
			case 0: return [v, t, p];
			case 1: return [q, v, p];
			case 2: return [p, v, t];
			case 3: return [p, q, v];
			case 4: return [t, p, v];
			case 5: return [v, p, q];
		}
	}

	//	create a rat color object from hsv values
	rat.graphics.makeColorFromHSV = function (h, s, v)
	{
		var vals = hsv2rgb(h, s, v);
		var c = new rat.graphics.Color(vals[0] * 255, vals[1] * 255, vals[2] * 255);
		c.applyLimits();
		return c;
	};
	
	//	todo: expose these rgb and luv functions
	
	//	http://framewave.sourceforge.net/Manual/fw_function_020_0060_00330.html
	
	function rgb2xyz(ir, ig, ib)
	{
		var r = ir/255;
		var g = ig/255;
		var b = ib/255;
		return {
			x: 0.412453*r + 0.35758 *g + 0.180423*b,
			y: 0.212671*r + 0.71516 *g + 0.072169*b,
			z: 0.019334*r + 0.119193*g + 0.950227*b
		};
	};
	
	//	Computed L component values are in the range [0 to 100].
	//	Computed U component values are in the range [-124 to 220].
	//	Computed V component values are in the range [-140 to 116].
	function xyz2luv(xyz)
	{
		var xn = 0.312713;
		var yn = 0.329016;
		var bigYN = 1.0;

		var un = 4*xn / (-2*xn + 12*yn + 3);
		var vn = 9*yn / (-2*xn + 12*yn + 3);
		var u = 4*xyz.x / (xyz.x + 15*xyz.y + 3*xyz.z);
		var v = 9*xyz.y / (xyz.x + 15*xyz.y + 3*xyz.z);
		//var L = 116 * (Y/bigYN)^(1/3) - 16;
		var L = 116 * Math.pow(xyz.y/bigYN, 1/3) - 16;
		var U = 13*L*(u-un);
		var V = 13*L*(v-vn);
		return {
			l:L,
			u:U,
			v:V
		};
	};
	
	//	http://framewave.sourceforge.net/Manual/fw_function_020_0060_00340.html
	function luv2xyz(luv)
	{
		var xn = 0.312713;
		var yn = 0.329016;
		var bigYN = 1.0;
		
		var un = 4*xn / (-2*xn + 12*yn + 3);	//	these should be constants.
		var vn = 9*yn / (-2*xn + 12*yn + 3);
		
		var l = luv.l;//luv.l/100;
		var u = luv.u;//(luv.u+134)/354;	//	this seems off by 10, but it's what everyone does
		var v = luv.v;//(luv.v+140)/256;
		
		var up = u / ( 13 * l) + un;
		var vp = v / ( 13 * l) + vn;
		
		var Y = Math.pow(bigYN * (( l + 16 ) / 116 ), 3);
		var X = - 9 * Y * up / (( up - 4 ) * vp - up*vp );
		var Z = ( 9 * Y - 15 * vp * Y - vp * X ) / (3 * vp);
		
		return {x:X, y:Y, z:Z};
	};
	function xyz2rgb(luv)
	{
		return {
			r: (3.240479*luv.x + -1.537150 *luv.y + -0.498535*luv.z) * 255,
			g: (-0.969256*luv.x + 1.875992 *luv.y + 0.041556*luv.z) * 255,
			b: (0.055648*luv.x + -0.204043*luv.y + 1.057311*luv.z) * 255
		};
	};
	
	function rgb2luv(ir, ig, ib)
	{
		return xyz2luv(rgb2xyz(ir, ig, ib));
	};
	function luv2rgb(luv)
	{
		return xyz2rgb(luv2xyz(luv));
	};
	
	/*	test code
	var xyz = rgb2xyz(64, 1, 95);
	var luv = xyz2luv(xyz);
	
	var xyz2 = luv2xyz(luv);
	var xtmp = xyz2rgb(xyz2);
	console.log("color test " + xtmp.r + "," + xtmp.g + "," + xtmp.b);
	*/

	//	a bunch of standard colors
	rat.graphics.transparent = new rat.graphics.Color(0, 0, 0, 0.0);
	rat.graphics.black = new rat.graphics.Color(0, 0, 0);
	rat.graphics.white = new rat.graphics.Color(255, 255, 255);
	rat.graphics.gray = new rat.graphics.Color(128, 128, 128);
	rat.graphics.lightGray = new rat.graphics.Color(190, 190, 190);
	rat.graphics.darkGray = new rat.graphics.Color(64, 64, 64);

	rat.graphics.red = new rat.graphics.Color(255, 0, 0);
	//	note: see in named color list below that this is called "lime",
	//	which is really annoying, since it's already "green" in RGB,
	//	but they use the name "green" to refer to 0,128,0.  Whatever.
	//	I'm leaving this definition of green.
	//	our "dark" and "light" values don't necessarily matched standard named colors below,
	//	either.
	rat.graphics.green = new rat.graphics.Color(0, 255, 0);
	rat.graphics.blue = new rat.graphics.Color(0, 0, 255);

	rat.graphics.yellow = new rat.graphics.Color(255, 255, 0);
	rat.graphics.cyan = new rat.graphics.Color(0, 255, 255);
	rat.graphics.violet = new rat.graphics.Color(255, 0, 255);
	rat.graphics.magenta = rat.graphics.violet;
	
	rat.graphics.lightRed = new rat.graphics.Color(255, 128, 128);
	rat.graphics.darkRed = new rat.graphics.Color(128, 0, 0);

	rat.graphics.lightGreen = new rat.graphics.Color(128, 255, 128);
	rat.graphics.darkGreen = new rat.graphics.Color(0, 128, 0);

	rat.graphics.lightBlue = new rat.graphics.Color(128, 128, 256);
	rat.graphics.darkBlue = new rat.graphics.Color(0, 0, 128);

	rat.graphics.darkYellow = new rat.graphics.Color(128, 128, 0);
	rat.graphics.darkCyan = new rat.graphics.Color(0, 128, 128);
	rat.graphics.darkViolet = new rat.graphics.Color(128, 0, 128);
	rat.graphics.darkMagenta = rat.graphics.darkViolet;

	rat.graphics.lightYellow = new rat.graphics.Color(255, 255, 128);
	rat.graphics.lightCyan = new rat.graphics.Color(128, 255, 255);
	rat.graphics.lightViolet = new rat.graphics.Color(255, 128, 255);
	rat.graphics.lightMagenta = rat.graphics.lightViolet;

	rat.graphics.orange = new rat.graphics.Color(255, 128, 0);

	rat.graphics.brown = new rat.graphics.Color(128, 96, 0);
	rat.graphics.darkBrown = new rat.graphics.Color(96, 64, 0);

	//	make a rat color from rgb style string
	rat.graphics.Color.makeFromStyleString = function (styleString)
	{
		var r, g, b;
		r = g = b = 255;
		var a = 1.0;
		if (styleString.substring(0,3) === 'rgb')
		{
			var startIndex = styleString.indexOf('(');
			if (startIndex)	//	make sure
			{
				styleString = styleString.substring(startIndex + 1);
				var nextIndex = styleString.indexOf(',');
				r = parseInt(styleString.substring(0, nextIndex), 10);
				styleString = styleString.substring(nextIndex + 1);

				nextIndex = styleString.indexOf(',');
				g = parseInt(styleString.substring(0, nextIndex), 10);
				styleString = styleString.substring(nextIndex + 1);

				nextIndex = styleString.indexOf(',');
				if (nextIndex < 0)
					nextIndex = styleString.indexOf(')');
				else // there's an alpha value - just grab it now, and let parseFloat ignore trailing ); whatever
					a = parseFloat(styleString.substring(nextIndex + 1));

				b = parseInt(styleString.substring(0, nextIndex), 10);
			}
			//rgba(23, 86, 89, 1)

		} else if (rat.graphics.Color.isStandard(styleString))
		{
			return rat.graphics.Color.standard(styleString);
		} else
		{
			// default to hex color style string like #FF8020
			//	We support an optional leading #
			//	Note that some browsers do not support leaving that off,
			//	so it's best to include the # as a habit.
			if (styleString.charAt(0) === '#')
				styleString = styleString.substring(1, 7);
			r = parseInt(styleString.substring(0, 2), 16);
			g = parseInt(styleString.substring(2, 4), 16);
			b = parseInt(styleString.substring(4, 6), 16);
		}
		
		return new rat.graphics.Color(r, g, b, a);
	};
	rat.graphics.Color.makeFromHexString = rat.graphics.Color.makeFromStyleString;	//	variant name for this function

	//	standard style colors by name
	rat.graphics.Color.standardColorTable = {
			"aliceblue": "#f0f8ff", "antiquewhite": "#faebd7", "aqua": "#00ffff", "aquamarine": "#7fffd4", "azure": "#f0ffff", "beige": "#f5f5dc",
			"bisque": "#ffe4c4", "black": "#000000", "blanchedalmond": "#ffebcd", "blue": "#0000ff", "blueviolet": "#8a2be2", "brown": "#a52a2a",
			"burlywood": "#deb887", "cadetblue": "#5f9ea0", "chartreuse": "#7fff00", "chocolate": "#d2691e", "coral": "#ff7f50", "cornflowerblue": "#6495ed",
			"cornsilk": "#fff8dc", "crimson": "#dc143c", "cyan": "#00ffff", "darkblue": "#00008b", "darkcyan": "#008b8b", "darkgoldenrod": "#b8860b",
			"darkgray": "#a9a9a9", "darkgreen": "#006400", "darkkhaki": "#bdb76b", "darkmagenta": "#8b008b", "darkolivegreen": "#556b2f",
			"darkorange": "#ff8c00", "darkorchid": "#9932cc", "darkred": "#8b0000", "darksalmon": "#e9967a", "darkseagreen": "#8fbc8f",
			"darkslateblue": "#483d8b", "darkslategray": "#2f4f4f", "darkturquoise": "#00ced1", "darkviolet": "#9400d3", "deeppink": "#ff1493",
			"deepskyblue": "#00bfff", "dimgray": "#696969", "dodgerblue": "#1e90ff", "firebrick": "#b22222", "floralwhite": "#fffaf0",
			"forestgreen": "#228b22", "fuchsia": "#ff00ff", "gainsboro": "#dcdcdc", "ghostwhite": "#f8f8ff", "gold": "#ffd700", "goldenrod": "#daa520",
			"gray": "#808080", "green": "#008000", "greenyellow": "#adff2f", "honeydew": "#f0fff0", "hotpink": "#ff69b4", "indianred ": "#cd5c5c",
			"indigo ": "#4b0082", "ivory": "#fffff0", "khaki": "#f0e68c", "lavender": "#e6e6fa", "lavenderblush": "#fff0f5", "lawngreen": "#7cfc00",
			"lemonchiffon": "#fffacd", "lightblue": "#add8e6", "lightcoral": "#f08080", "lightcyan": "#e0ffff", "lightgoldenrodyellow": "#fafad2",
			"lightgray": "#d3d3d3", "lightgreen": "#90ee90", "lightpink": "#ffb6c1", "lightsalmon": "#ffa07a", "lightseagreen": "#20b2aa",
			"lightskyblue": "#87cefa", "lightslategray": "#778899", "lightsteelblue": "#b0c4de", "lightyellow": "#ffffe0", "lime": "#00ff00",
			"limegreen": "#32cd32", "linen": "#faf0e6", "magenta": "#ff00ff", "maroon": "#800000", "mediumaquamarine": "#66cdaa", "mediumblue": "#0000cd",
			"mediumorchid": "#ba55d3", "mediumpurple": "#9370d8", "mediumseagreen": "#3cb371", "mediumslateblue": "#7b68ee",
			"mediumspringgreen": "#00fa9a", "mediumturquoise": "#48d1cc", "mediumvioletred": "#c71585", "midnightblue": "#191970", "mintcream": "#f5fffa",
			"mistyrose": "#ffe4e1", "moccasin": "#ffe4b5", "navajowhite": "#ffdead", "navy": "#000080",
			"oldlace": "#fdf5e6", "olive": "#808000", "olivedrab": "#6b8e23", "orange": "#ffa500", "orangered": "#ff4500", "orchid": "#da70d6",
			"palegoldenrod": "#eee8aa", "palegreen": "#98fb98", "paleturquoise": "#afeeee", "palevioletred": "#d87093", "papayawhip": "#ffefd5",
			"peachpuff": "#ffdab9", "peru": "#cd853f", "pink": "#ffc0cb", "plum": "#dda0dd", "powderblue": "#b0e0e6", "purple": "#800080",
			"red": "#ff0000", "rosybrown": "#bc8f8f", "royalblue": "#4169e1", "saddlebrown": "#8b4513", "salmon": "#fa8072", "sandybrown": "#f4a460",
			"seagreen": "#2e8b57", "seashell": "#fff5ee", "sienna": "#a0522d", "silver": "#c0c0c0", "skyblue": "#87ceeb", "slateblue": "#6a5acd",
			"slategray": "#708090", "snow": "#fffafa", "springgreen": "#00ff7f", "steelblue": "#4682b4", "tan": "#d2b48c", "teal": "#008080",
			"thistle": "#d8bfd8", "tomato": "#ff6347", "turquoise": "#40e0d0", "violet": "#ee82ee", "wheat": "#f5deb3", "white": "#ffffff",
			"whitesmoke": "#f5f5f5", "yellow": "#ffff00", "yellowgreen": "#9acd32"
		};
	
	//	Is this a standard color name?
	//	If so, return the hex version of its style string.
	//	otherwise, return null (so it behaves like a boolean test, if you want)
	rat.graphics.Color.isStandard = function (colorName)
	{
		var cName = colorName.toLowerCase();
		var colors = rat.graphics.Color.standardColorTable;
		if (colors[cName])
			return colors[cName];
		return null;
	};
	
	//	return a rat graphics color from a standard style name
	rat.graphics.Color.standard = function (colorName)
	{
		var colors = rat.graphics.Color.standardColorTable;
		
		var cName = colorName.toLowerCase();
		if (colors[cName])
			return rat.graphics.Color.makeFromHexString(colors[cName]);

		return rat.graphics.gray;
	};
	
	
	//	fix color style formatting.
	//	sometimes, leaving # off a color style will work, and sometimes not,
	//	so make sure it has # if it looks like it's a standard hex number style.
	rat.graphics.fixColorStyle = function(c) {
		//	if leading character of 
		if (typeof(c) === 'string' && c.charAt(0) >= '0' && c.charAt(0) <= '9')
			return "#" + c;
		else
			return c;
	};
		
});

//--------------------------------------------------------------------------------------------------
//
//	Single screen class.
//	See r_screenmanager for screen stack management
//

//----------------------------
//	Screen
rat.modules.add( "rat.ui.r_screen",
[
	{ name: "rat.utils.r_utils", processBefore: true },
	{ name: "rat.ui.r_ui", processBefore: true },
	{ name: "rat.graphics.r_graphics", processBefore: true },
	
	"rat.ui.r_ui_shape",
	"rat.ui.r_screenmanager",
	"rat.input.r_input",
	"rat.input.r_inputmap",
], 
function(rat)
{
	/**@constructor
	 * @extends rat.ui.Element
	 * @param {?} shapeType */
	rat.ui.Screen = function ( shapeType )	//	unused param?  Huh?
	{
		rat.ui.Screen.prototype.parentConstructor.call(this); //	default init
		this.setBounds(0, 0, rat.graphics.SCREEN_WIDTH, rat.graphics.SCREEN_HEIGHT);
	};
	rat.utils.inheritClassFrom(rat.ui.Screen, rat.ui.Element);

	//	Default class properties (DO NOT ADD OBJECTS/ARRAYS HERE!  THEY BECOMRE STATIC!)
	rat.ui.Screen.prototype.modal = false;
	rat.ui.Screen.prototype.inputMap = null;	//	optional inputmap for key/controller inputs
	
	//	simple alternative to inputmaps - also optional.
	//	This is a list of current targets for all users, not a list of possible targets
	rat.ui.Screen.prototype.targets = null;
	
	rat.ui.Screen.prototype.allowClickAway = false;	//	allow a click outside the screen to dismiss it.  useful for touch UI.
	rat.ui.Screen.prototype.allowBackClose = false;	//	automatically support 'back' ui button (e.g. ESC key) to close a window, like clickAway
	//	Variables modified and managed via the screen manager
	rat.ui.Screen.prototype.isDeactivated = true;	//	Screens do not start active.
	rat.ui.Screen.prototype.isOverlay = true;		//	Old functionality seems to indicate that all screens were overlay screen
	rat.ui.Screen.prototype.isSuspended = true;	//	screen start suspended.
	rat.ui.Screen.prototype.savedTarget = void 0;	//	When the current target gets saved, save it here.

	//	Add a prop to screen to support the old var name fullOpaque which is the reverse of isOverlay
	rat.utils.addProp(rat.ui.Screen.prototype, 'fullOpaque',
		function (v)
		{
			this.setOverlay( !v );
		},
		function ()
		{
			return !this.isOverlay;
		});

	//	Cleanup the screen
	rat.ui.Screen.prototype.destroy = function()
	{
	};

	/**  Set if this screen is currently an overlay screen
 */
	rat.ui.Screen.prototype.setOverlay = function (isOverlay)
	{
		this.isOverlay = isOverlay;
	};

	/**  Deactivate this screen if is is not already deactivated
 */
	rat.ui.Screen.prototype.deactivate = function (options)
	{
		if (options === true)
			options = { allowOnlySuspend: true };
		else
			options = options || {};
		
		//	Handle a "light" deactivate
		if (!this.isSuspended)
		{
			this.isSuspended = true;
			if (this.screenSuspend)
			{
				this.screenSuspend();
			}
		}

		//	Only suspend only works if the screen HAS a suspend
		if (options.allowOnlySuspend && this.screenSuspend)
			return;

		//	Now deactivate
		if (!this.isDeactivated)
		{
			this.isDeactivated = true;
			if (this.screenDeactivate)
			{
				this.screenDeactivate();
			}
		}	
	};

	/**  Activate this screen if is is not already active
 */
	rat.ui.Screen.prototype.activate = function ()
	{
		if (this.isDeactivated)
		{
			this.isDeactivated = false;
			if (this.screenActivate)
			{
				this.screenActivate();
			}
		}

		//	Don't forget to resume
		if (this.isSuspended)
		{
			this.isSuspended = false;
			if (this.screenResume)
			{
				this.screenResume();
			}
		}
	};

	rat.ui.Screen.prototype.setModal = function (isModal)
	{
		this.modal = isModal;
	};
	rat.ui.Screen.prototype.isModal = function ()
	{
		return this.modal;
	};

	rat.ui.Screen.prototype.setAllowClickAway = function (allowClickAway)
	{
		this.allowClickAway = allowClickAway;
	};
	rat.ui.Screen.prototype.setAllowBackClose = function (allowBackClose)
	{
		this.allowBackClose = allowBackClose;
	};

	//	expand me to full screen display
	rat.ui.Screen.prototype.expandToDisplay = function ()
	{
		this.setPos(0, 0);
		this.setSize(rat.graphics.SCREEN_WIDTH, rat.graphics.SCREEN_HEIGHT);
	};
	
	//	center me in full screen display
	rat.ui.Screen.prototype.centerInDisplay = function ()
	{
		var size = this.getSize();
		var x = Math.floor((rat.graphics.SCREEN_WIDTH - size.w)/2);
		var y = Math.floor((rat.graphics.SCREEN_HEIGHT - size.h)/2);
		this.setPos(x, y);
	};
	
	//	position me in standard good spot for popup (top 1/3 of screen)
	rat.ui.Screen.prototype.centerHighInDisplay = function ()
	{
		var size = this.getSize();
		var x = Math.floor((rat.graphics.SCREEN_WIDTH - size.w)/2);
		var y = Math.floor((rat.graphics.SCREEN_HEIGHT - size.h)/3);
		this.setPos(x, y);
	};
	
	//	Set this element as the current direct input target for this screen.
	//	e.g. if the user clicked directly on an edittext box,
	//	that should be the current input target.
	//	If we have an inputmap, use that.
	//	It's OK to pass a null new target, to clear any current target.
	rat.ui.Screen.prototype.setCurrentTarget = function (newTarget, sourceEvent) {
		if (this.inputMap) {
			this.inputMap.focusByButton(newTarget, true);
		} else {
			//	simpler direct target support... (only semi-tested)
			//	This is for when somebody sets up a screen without an input map, but it does have
			//	things in it like edit text boxes, and they need to become a target for keyboard inputs!
			if (!this.targets)
				this.targets = [];
			//	todo: check input index from sourceEvent, if any.
			var oldTarget = this.targets[0];
			if (oldTarget && oldTarget !== newTarget)
			{
				if (oldTarget.Blur)
					oldTarget.Blur();
				else if (oldTarget.blur)
					oldTarget.blur();
			}
			this.targets[0] = newTarget;
			if (newTarget && newTarget !== oldTarget)
				newTarget.focus();
		}
		
	};
	
	//	return this screen's current target (or null if none)
	rat.ui.Screen.prototype.getCurrentTarget = function (inputIndex) {
		inputIndex = inputIndex || 0;
		if (this.inputMap)
			return this.inputMap.getTarget();
		else {
			if (this.targets && this.targets[inputIndex])
				return this.targets[inputIndex];
		}
		return null;
	}
	
	//	Save the current target.
	rat.ui.Screen.prototype.saveCurrentTarget = function () {
		if (this.inputMap) {
			this.savedTarget = this.inputMap.map[this.inputMap.index];
		}
	};

	//	Restore the saved target (if there isn't one, select the first)
	rat.ui.Screen.prototype.restoreSavedTarget = function () {
		if (!this.inputMap)
			return;
		var index = 0;
		var saved = this.savedTarget;
		this.savedTarget = void 0;
		if (saved) {
			var map = this.inputMap.map;
			for (var testIndex = 0; testIndex !== map.length; ++testIndex) {
				if (map[testIndex] === saved) {
					index = testIndex;
					break;
				}
			}
		}
		this.inputMap.focusButton(index, true);
	};

	rat.ui.Screen.prototype.handleUIInput = function (event)
	{
		function isUIDirection(which)
		{
			if (which === 'up' || which === 'down' || which === 'left' || which === 'right' || which === 'enter')
				return true;
			else
				return false;
		}

		//console.log("screen ui " + event.which);

		//	See if we have an input map.  If so, handle direction inputs nicely
		//	TODO:  We're ignoring ratInputIndex here?
		//	only give inputMap UI navigation events, and only directions.
		//	we handle 'enter' key presses ourselves in button event handling code.
		if (this.inputMap && event.eventType === 'ui' && isUIDirection(event.which))
		{
			//	KLUDGE:  This is temp... translate 'enter' to 'select'
			//	note that I'm going to remove that entirely from this solution, as soon as I have targeting working,
			//	since I want that to be handled elsewhere.
			if (event.which === 'enter')
				event.which = 'select';
			return this.inputMap.handleDirection(event.which);
		} else if (event.which === 'back' && this.allowBackClose)
		{
			rat.screenManager.popScreen();
			return true;    //  handled (closed)
		}
		return false;
	};
	
	//	handle some keys specially.
	rat.ui.Screen.prototype.handleKeyDown = function (ratEvent)
	{
		//	could invent a new ui input concept of "next targetable input" and "previous targetable input" events.
		//	But let's just do this for now - keyboard tab support is standard.
		if (ratEvent.which === rat.keys.tab && this.inputMap) {
			return this.inputMap.handleTab(ratEvent);
		}
		
		//	inherited behavior
		return rat.ui.Screen.prototype.parentPrototype.handleKeyDown.call(this, ratEvent);
	};

	//	Some functions related to building inputmap.
	//	Probably move this to another module.
	//	Maybe inputmap itself, or a new module specifically for constructing inputmaps or otherwise processing screen layout
	var targetList;
	
	function addToList()
	{
		//	OK to use "this" here?  this function is being called with "call", so it should be OK.
		//	Seems to work.
		var el = this;
		if (el.isTargetable() && el.isEnabled() && el.isVisible())
		{
			var entry = {
				globalPos: el.getGlobalPos(),
				rect: el.getBounds(el.tempRect),
				el: el
			};
			entry.pos = {
				x: entry.globalPos.x + entry.rect.w / 2,
				y: entry.globalPos.y + entry.rect.h / 2,
			};
			targetList.push( entry );
		}
	}

	//	do a single pass looking for best option in one direction.
	function singlePass(skipIndex, startPos, skipMe, searchAngleRatio, dir, directionPreference)
	{
		var bestDist = 0;
		var bestIndex = -1;
		var arcSize = 1 / searchAngleRatio;
		for (var i = 0; i !== targetList.length; ++i)
		{
			//	don't target self?
			if (skipMe && i === skipIndex)
				continue;

			var e = targetList[i];
			var tpos = e.pos;

			var dx = tpos.x - startPos.x;
			var dy = tpos.y - startPos.y;
			var arcSizeDy = (dy < 0 ? -dy : dy) * arcSize;
			var arcSizeDx = (dx < 0 ? -dy : dx) * arcSize;

			//	this value is somewhat arbitrary - it narrows the size of our arc,
			//	by increasing the opposite direction we're competing against...
			//	arcSize 1 = 45-degree angle, 2 = narrow
			//	so, convert from "searchAngleRatio" to arcSize
			if ( (dir === 'right' && dx > 0 && dx > arcSizeDy) ||
				 (dir === 'left' && dx < 0 && -dx > arcSizeDy) ||
				 (dir === 'down' && dy > 0 && dy > arcSizeDx) ||
				(dir === 'up' && dy < 0 && -dy > arcSizeDx) )
			{
				//	experiment:  I don't want to narrow the arc too tight and eliminate buttons,
				//	but let's give a higher weight to the x/y value that matches the direction we're looking,
				//	e.g. if we're looking left, count x values as closer than y values.
				//	e.g. if directionPreference is 2, then we count aligned directions twice as strongly
				//	Initial tests are good.  This is an effective approach.
				if (dir === 'right' || dir === 'left')
					dx /= directionPreference;
				else
					dy /= directionPreference;

				var dist2 = dx * dx + dy * dy;
				if (bestIndex < 0 || dist2 < bestDist)
				{
					bestIndex = i;
					bestDist = dist2;
				}
			}
		}

		return bestIndex;
	}

	function findNearestTargetInDirection(skipIndex, pos, dir, searchAngleRatio, directionPreference, wrapHoriz, wrapVert, screen)
	{
		//	do our first pass normally - look from where we are in the given direction.
		var bestIndex = singlePass(skipIndex, pos, true, searchAngleRatio, dir, directionPreference);

		//	if near button not found, and we support wrapping around,
		//	search a second time with our point reset to the appropriate opposite edge.
		if (bestIndex < 0 && (wrapHoriz || wrapVert))
		{
			//	important:  This needs to happen in global space, like everything else here,
			//	and keeping in mind the fact that the screen may not be at 0,0.
			//var screenPos = screen.getGlobalPos();
			//	hmm...  to get to opposite side, just add/subtract screen size.
			//	this puts us PAST the edge, which is good anyway, since it gives us leeway for our arc check to find things nearby,
			//	instead of starting right at the edge and not finding reasonably nearby stuff.
			//	If you do want to change that, you'll need screenPos above, to be in the right global space.

			//	Note:  This does not currently work well at all with buttons inside a big scrollview.
			//		In that case, what we really should be doing is factoring in the content size of the scrollview,
			//		but that's complicated here since we're in GLOBAL space, not parent content space, and who knows how many levels up
			//		the scrollview might be...  Find a way to fix this.

			var pos2 = { x: pos.x, y: pos.y };
			//	wrap
			if (dir === 'right' && wrapHoriz)
				pos2.x -= screen.size.x;	//	left edge
			if (dir === 'left' && wrapHoriz)
				pos2.x += screen.size.x;	//	right edge
			if (dir === 'down' && wrapVert)
				pos2.y -= screen.size.y;	//	top edge
			if (dir === 'up' && wrapVert)
				pos2.y += screen.size.y;	//	bottom edge

			//	This is subtle, but when wrapping, this button might really be its own best target,
			//		rather than another button near it.
			//		For instance, imagine a simple aligned column of buttons...
			//		you'd want left/right wrapping to just go to the same button rather than one above/below it.
			//	So, when wrapping, allow me to be my own target, if that really does turn out best.

			//	do second (wrapped) pass
			bestIndex = singlePass(skipIndex, pos2, false, searchAngleRatio, dir, directionPreference);
		}

		//	did we get something with either pass?
		if (bestIndex >= 0)
		{
			return targetList[bestIndex].el;
		}

		return null;
	}

	//	given a bunch of elements inside this screen,
	//	build an input map automatically based on positions.
	//	Notable issues: the positioning relies on e.size,
	//		so if an element's size is image-based,
	//		and the image has not yet fully loaded by the time this function
	//		is called, the size ends up being 0,0 and causing issues
	rat.ui.Screen.prototype.autoBuildInputMap = function (wrapHoriz, wrapVert, searchAngleRatio, directionPreference)
	{
		var screen = this;
		this.inputMap = null;
		
		if (!this.subElements)	//	no controls at all, don't bother
			return;
		
		//	some argument defaults
		if (wrapHoriz === void 0)
			wrapHoriz = true;
		if (wrapVert === void 0)
			wrapVert = true;
		if (searchAngleRatio === void 0)
			searchAngleRatio = 1;	//	see below - specify a large searchAngleRatio to allow nearby buttons to be tested at all.
		if (directionPreference === void 0)
			directionPreference = 2;	//	see below
		
		//	build target list first, recursively.
		targetList = []; // This is a hidden variable so it can be exposed to other functions in this file
	
		this.applyRecursively(addToList, targetList);
		if (targetList.length <= 0)
			return;
		
		//	now check each control, looking for up/left/right/down controls.
		var map = [];
		for (var i = 0; i < targetList.length; i++)
		{
			var e = targetList[i];
			var entry = {currObj : e.el};
			var pos = e.pos;//getGlobalPos();
			//pos.x += e.getBounds().w / 2;
			//pos.y += e.getBounds().h / 2;
			
			entry.up = findNearestTargetInDirection(i, pos, 'up', searchAngleRatio, directionPreference, wrapHoriz, wrapVert, screen);
			entry.down = findNearestTargetInDirection(i, pos, 'down', searchAngleRatio, directionPreference, wrapHoriz, wrapVert, screen);
			entry.left = findNearestTargetInDirection(i, pos, 'left', searchAngleRatio, directionPreference, wrapHoriz, wrapVert, screen);
			entry.right = findNearestTargetInDirection(i, pos, 'right', searchAngleRatio, directionPreference, wrapHoriz, wrapVert, screen);
			
			map.push(entry);
			
			//	and keep track of where that went, for temp use in tab order below...
			e.mapEntry = entry;
			
			//console.log("map " + i);
			//console.log(entry);
		}
		//	create that input map (and try to guess at correct default)
		var defaultItem = -1;
		if (rat.input.useLastUIInputType && (rat.input.lastUIInputType === 'keyboard' || rat.input.lastUIInputType === 'controller'))
			defaultItem = 0;
		
		//	Set up default tab order based on screen layout.
		//	left-to-right and top to bottom.
		//	basically, we just need to sort our target list...
		//	A more sophisticated version would maybe check angles and arcs and things like the above.  *sigh*
		targetList.sort(function(a, b)
			{
				if (a.pos.y < b.pos.y)
					return -1;
				else if (a.pos.y > b.pos.y)
					return 1;
				else {
					if (a.pos.x < b.pos.x)
						return -1;
					return 1;
				}
			}
		);
		for (var i = 0; i < targetList.length; i++)
		{
			var t = targetList[i];
			t.mapEntry.tabOrder = i;
			
			//console.log("" + i + ": " + t.pos.x + "," + t.pos.y + "    " + t.el.name);
		}
		
		//	create inputmap object with that mapping.
		this.inputMap = new rat.InputMap(map, defaultItem);
		
		//	temp debug:
		//this.inputMap.dumpList();
		
		/*
		//	for now, let's pretend they're vertical, just to get things going!
		//	reference:
		//  var buttonBack = { currObj: this.backButton, down: this.adsButton, right: this.adsButton }
		//  var buttonAds = { currObj: this.adsButton, up: this.backButton, left: this.backButton }
		//  var map = [buttonBack, buttonAds]

		var map = [];
		for (var i = 0; i < targetList.length; i++)
		{
			var up = null;
			var down = null;
			if (i > 0)
				up = targetList[i-1];
			else
				up = targetList[targetList.length-1];
			if (i < targetList.length-1)
				down = targetList[i+1];
			else
				down = targetList[0];
			var entry = {currObj : targetList[i], up: up, down: down};
			map.push(entry);
		}
		this.inputMap = new rat.InputMap(map, -1);
		*/

		targetList = void 0;

		return this.inputMap;
	};

	//	OK, here's some trickiness.
	//	Until we have a functional "target" system, I'm going to do this.
	//	Look for a scroll view inside this screen.  Is there one?  If so, send mouse wheel events to that.  :)
	//	If you need different behavior in your screen, just override this function.
	
	//	STARTING OVER ON THIS:
	//		mouseWheel is sent to a visual target now, like clicks.
	//		you can still override this in your screen and redirect for yourself, if you like,
	//		e.g. let the user scroll a view when they're not OVER it.
	//		but by default, you scroll what you're hovering over, like most UI systems.
	/*
	rat.ui.Screen.prototype.handleMouseWheel = function (pos, ratEvent)
	{
		var found = this.applyRecursively(function ()
		{
			if (this.elementType === 'scrollView')
				return this;
			return false;
		});

		if (found)
			return found.handleMouseWheel(ratEvent);
		return false;
	};
	*/
});
//--------------------------------------------------------------------------------------------------
//
//	Screen management (screen stack, etc.)
//
//	TODO:
//		* get bottom screen (stack[0])
//		* get screen below this one
//		* get screen above this one
//		* get next-lower or top screen of certain type (popups vs. full screens)
//			what's the actual "screen" below these popups
//		* get next/prev screen by "type" meaning constructor
//
rat.modules.add( "rat.ui.r_screenmanager",
[
	{ name: "rat.os.r_events", processBefore: true },
	
	{ name: "rat.input.r_input", processBefore: true },
	"rat.debug.r_console",
	"rat.debug.r_profiler",
	"rat.graphics.r_graphics",
	"rat.ui.r_screen",
	"rat.ui.r_ui_textbox",
	"rat.ui.r_ui_button",
	"rat.utils.r_collision2d",
	"rat.utils.r_shapes",
	"rat.math.r_math",
],
function(rat)
{
	//	some common command numbers
	//	todo - move to rat.event namespace, or something like that.  system, at least.
	rat.OK_COMMAND = -100;
	rat.CANCEL_COMMAND = -99;

	rat.screenManager = {
		screenStack: [],	//	current active screens and popups

		regScreens: {},	//	(hash) optional screen registration system for easier switching between screens

		//	set root UI - replace all screens (if any) with a single current screen
		/** @suppress {missingProperties} - for screenDeactivate */
		setUIRoot: function (uiRoot)
		{
			//	Remove all of the current screens.
			rat.screenManager.popAllScreens();

			//	note:  if uiRoot is null, we're just being asked to clear stack, not put a new screen up.  That's fine.
			if(uiRoot)
			{
				//	set this as our only screen.  Use pushScreen function so we get side effects, like activation functions being called.
				rat.screenManager.pushScreen(uiRoot);
			}
		},

		//	get topmost screen
		getTopScreen: function ()
		{
			if(rat.screenManager.screenStack.length > 0)
				return rat.screenManager.screenStack[rat.screenManager.screenStack.length - 1];
			else
				return null;
		},

		//	push a screen onto the stack of screens
		pushScreen: function (screen)
		{
			if (!screen)
				return;

			//	Let the current screen save their target.
			var topScreen = rat.screenManager.getTopScreen();
			if (topScreen)
				topScreen.saveCurrentTarget();

			//	If we are not overlay screen, walk down the stack suspending any screens that are not yet suspended
			if (!screen.isOverlay)
			{
				var curScreen;
				for (var index = rat.screenManager.screenStack.length - 1; index >= 0; --index)
				{
					curScreen = rat.screenManager.screenStack[index];
					if (curScreen.isSuspended)
						break;
					curScreen.deactivate({ allowOnlySuspend: true });
				}
			
			} else if (screen.isModal) {
				//	let's clear tooltips so they don't draw or pop later...
				//	todo: flags to support/suppress this?
				var curScreen;
				for (var index = rat.screenManager.screenStack.length - 1; index >= 0; --index)
				{
					curScreen = rat.screenManager.screenStack[index];
					//	I can't just clear activeTooltip for a few reasons,
					//	including the fact that it's NULLed out every frame by this point.
					//	Anyway, this is what we want to do so we don't draw mouse tracking graphics
					//	when the mouse really isn't in lower-level screens any more.
					//	This is close to correct, but we still have some things like buttons behaving wrong...
					//	We either need them to respect mouseLeave(),
					//	or we need some new concept of let-go-of-mouse-tracking.
					//	see notes in r_ui.js about optimizing mouse tracking, and handleMouseLeave.
					curScreen.applyRecursively(function (arg){
						if (this.toolTip)
							this.toolTip.timer = 0;
						this.mouseLeave();
					});
				}
			}

			//	Put me on the top of the screen stack, and activate me

			rat.screenManager.screenStack.push(screen);
			screen.activate();
		},

		//	insert a screen into the stack of screens
		insertScreen: function (screen)
		{
			// not the top screen, so tell it to suspend until it's on top
			if (rat.screenManager.screenStack.length)
			{
				screen.deactivate({ allowOnlySuspend: true });
				rat.screenManager.screenStack.unshift(screen);
				//rat.console.log("insertScreen: " + rat.screenManager.screenStack.length);
			}

			//	It really is the only screen, so add it.
			else
				rat.screenManager.pushScreen(screen);
			
		},

		//	remove a screen from the stack by matching ids
		removeScreen: function (screen)
		{
			for(var i = 0; i < rat.screenManager.screenStack.length; ++i)
			{
				if(rat.screenManager.screenStack[i].id === screen.id)
				{
					//	If it is the top screen, call pop
					if (i === rat.screenManager.screenStack.length - 1)
						return rat.screenManager.popScreen();
					else
						return rat.screenManager.removeScreenAtIndex(i);
					break;
				}
			}

			//	Nothing to remove
			return void 0;
		},

		// Remove a screen at the given index
		removeScreenAtIndex: function( stackIndex )
		{
			if (stackIndex < 0 || stackIndex >= rat.screenManager.screenStack.length)
				return void 0;
			var screen = rat.screenManager.screenStack[stackIndex];
			rat.screenManager.screenStack.splice(stackIndex, 1);
			screen.deactivate();
			screen.destroy();
			return screen;
		},

		//
		//	pop top-most screen off stack
		//
		popScreen: function ()
		{
			//	Remove the top screen
			var screen = rat.screenManager.removeScreenAtIndex(rat.screenManager.screenStack.length - 1);

			//	Reactive (or resume) the screen until a non-overlay
			var curScreen;
			for (var i = rat.screenManager.screenStack.length-1; i >= 0; --i)
			{
				curScreen = rat.screenManager.screenStack[i];
				if( curScreen )
				{
					curScreen.activate();

					//	Only activate until we hit a screen that covers the rest
					if( !curScreen.isOverlay )
						break;
				}
			}

			//	Get the new top of stack and restore their target
			var topScreen = rat.screenManager.getTopScreen();
			if (topScreen)
				topScreen.restoreSavedTarget();

			//	Return the removed screen
			return screen;
		},

		/** 	Remove all screens
 */
		popAllScreens: function ()
		{
			//	Not using removeScreenAtIndex so we don't have to deal with changing the array.
			for (var i = rat.screenManager.screenStack.length-1; i >= 0; --i)
			{
				rat.screenManager.screenStack[i].deactivate();
			}

			//	No more screens.
			rat.screenManager.screenStack = [];
		},

		//	register a standard screen for easy creation/switching later
		registerScreen: function (name, creator)
		{
			rat.screenManager.regScreens[name] = { creator: creator };
		},

		//	switch to a registered screen
		//	pushOn (which defaults to undefined) indicates that we want to push this new screen on the stack instead of replacing.
		switchToScreen: function (name, pushOn, args)
		{
			args = args || [];
			//	Find the registered screen.
			var regScreen = rat.screenManager.regScreens[name];
			if(!regScreen)
			{
				rat.console.log("ERROR: no such registered screen: " + name);
				return null;
			}

			//	Create the screen.
			var screen = regScreen.creator.apply(void 0, args);
			if(!screen)
			{
				rat.console.log("Error: screen creator failed " + name);
				return null;
			}
			screen._screenTag = name;

			//	If we are replacing the current, pop it off.
			if(!pushOn)	//	not pushing - kill old screen first
				rat.screenManager.popScreen();

			//	finally, push new screen
			rat.screenManager.pushScreen(screen);
			return screen;
		},

		//	pop up a standard yes/no dialog, using this setup structure.
		doConfirmDialog: function (setup)
		{
			//	we can add many things to this setup structure over time.  It's pretty minimal so far.
			//	But even better would be reading from a resource.

			//	a bunch of defaults...

			var width = setup.width;
			var height = setup.height;
			if(typeof width === 'undefined')
				width = 420;
			if(typeof height === 'undefined')
				height = 200;
			if(typeof setup.title === 'undefined')
				setup.title = "";
			if(typeof setup.body === 'undefined')
				setup.body = "";
			if(typeof setup.yesText === 'undefined')
				setup.yesText = "Yes";
			if(typeof setup.yesCommand === 'undefined')
				setup.yesCommand = rat.OK_COMMAND;
			if(typeof setup.noText === 'undefined')
				setup.noText = "";
			if(typeof setup.noCommand === 'undefined')
				setup.noCommand = rat.CANCEL_COMMAND;
			if(typeof setup.userData === 'undefined')
				setup.userData = null;
			if (!setup.bgColor)
				setup.bgColor = { r: 80, g: 80, b: 80 };
			if (!setup.frameColor)
				setup.frameColor = { r: 120, g: 120, b: 120 };
			if (!setup.pos)
				setup.pos = { x: rat.graphics.SCREEN_WIDTH / 2 - width, y: rat.graphics.SCREEN_WIDTH / 3 - height / 2 };
			if (!setup.titleFont)
				setup.titleFont = setup.font;
			if (!setup.bodyFont)
				setup.bodyFont = setup.font;
			if (!setup.buttonFont)
				setup.buttonFont = setup.font;
			if (!setup.titleFont) {
				setup.titleFont = {
					font: "Impact",
					size: 14
				};
			}
			if (!setup.bodyFont) {
				setup.titleFont = {
					font: "Arial",
					size: 12
				};
			}

			//	screen
			var screen = new rat.ui.Screen();
			screen.setModal(true);

			screen.setPos(setup.pos.x, setup.pos.y);
			screen.setSize(width, height);

			screen.setBackground(new rat.graphics.Color(setup.bgColor.r, setup.bgColor.g, setup.bgColor.b));

			screen.setFrame(4, new rat.graphics.Color(120, 120, 120));
			var b;
			var tbox;

			//	title
			if(setup.title !== "")
			{
				tbox = new rat.ui.TextBox(setup.title);
				tbox.setFont(setup.titleFont.font || setup.titleFont);
				tbox.setFontSize(setup.titleFont.size || 14);
				tbox.setPos(0, 0);
				tbox.setSize(screen.size.x, 14);
				tbox.setAlign(rat.ui.TextBox.alignCenter);
				tbox.setColor(new rat.graphics.Color(250, 250, 220));
				screen.appendSubElement(tbox);
			}

			//	body
			if(setup.body !== "")
			{
				tbox = new rat.ui.TextBox(setup.body);
				tbox.setFont(setup.bodyFont.font || setup.bodyFont);
				tbox.setFontSize(setup.bodyFont.size || 12);
				tbox.setPos(0, 40);
				tbox.setSize(screen.size.x, 14);
				//tbox.setAlign(rat.ui.TextBox.alignLeft);
				tbox.setAlign(rat.ui.TextBox.alignCenter);
				tbox.setColor(new rat.graphics.Color(190, 190, 190));
				screen.appendSubElement(tbox);
			}

			//	button
			var buttonPosY = screen.size.y * 2 / 3 - 30;
			if(setup.yesText !== "")
			{
				b = rat.ui.makeCheapButton(null, new rat.graphics.Color(150, 200, 150));
				b.setTextValue(setup.yesText);
				b.setSize(70, 30);
				b.setPos(50, buttonPosY);
				b.setCommand(setup.yesCommand, setup.userData);
				screen.appendSubElement(b);
			}

			//	button
			if(setup.noText !== "")
			{
				b = rat.ui.makeCheapButton(null, new rat.graphics.Color(150, 200, 150));
				b.setTextValue(setup.noText);
				b.setSize(70, 30);
				b.setPos(screen.size.x - 30 - 70, buttonPosY);
				b.setCommand(setup.noCommand, setup.userData);
				screen.appendSubElement(b);
			}

			rat.screenManager.pushScreen(screen);

			screen.handleCommand = function (command, info)
			{
				rat.screenManager.popScreen();	//	get rid of screen
				return false;	//	let the command continue to get passed up to somebody who will respond to it.
			};

			//	we can't really do modal, so just set it up, and let commands do their work.

			return screen;
		},

		//	dispatch an event down the screen stack.
		//	This means the usual walk through screens, top to bottom, but stop if one is modal,
		//	and send the event to the target of that screen.
		//	Note that "ratEvent" here has a controllerID value attached to it so we know which input device this came from.
		//	If anybody handles this event (returns true) stop dispatching it!
		//	return true if we handled it.
		//	This is a higher-level "ratEvent".  If you need system event info, get it from event.sysEvent
		dispatchEvent: function (event)
		{
			//	Hmm..  Why do we suppress ALL inputs when console is up?
			//	Console already had a chance to handle the event before this point (see rat.input.dispatchEvent)
			//	and if we got here, it's because the console said it was not its event.
			//	And in fact, sometimes we want the console up while we debug some events...
			//	So, I'm disabling this check 2016.11.18.  Tell me if I broke something!
			//if (rat.console.state.consoleActive === true || rat.console.state.consoleActive === "target" )
			//	return;
			
			var result = false;

			//console.log("screenmanager dispatchEvent " + event.eventType);
			//console.log("  which " + event.which);

			for(var i = rat.screenManager.screenStack.length - 1; i >= 0; i--)
			{
				//	sometimes, all of (or many of) the screens might get killed in response to an event in this loop,
				//	so it's possible to end up trying to dispatch to a screen that no longer exists.
				//	If we detect that case, stop dispatching.  Really, the people who handled the event should have returned that it was handled,
				//	but not every game is perfectly behaved in this way.
				if (i > rat.screenManager.screenStack.length - 1)
					return true;
				
				var screen = rat.screenManager.screenStack[i];
				if (typeof(screen) === 'undefined')
				{
					console.log("bogus screen at " + i);
					return;
				}
				var wasModal = screen.modal;
				//	If this screen is suspended, then don't handle events
				if (screen.isSuspended && !screen.forceScreenActive)
					break; // All other screens should be suspended.

				//	Let's see if we can direct this input to a direct target.
				var directTarget = null;
				
				//	see if this is a positional event (e.g. mouse click),
				//	because we don't want to use the targeting system for things like that.
				//	positional events are instead just handed down starting from the screen element.
				var isPositionalEvent = (event.eventType === "mousedown" ||
										event.eventType === "mouseup" ||
										event.eventType === "mousemove" ||
										event.eventType === "contextmenu" ||
										event.eventType === "touchstart" ||
										event.eventType === "touchend" ||
										event.eventType === "mousewheel" ||
										event.eventType === "touchmove" );
				if (!isPositionalEvent)
				{
					//	input map?  use that.
					if (screen.inputMap)
						directTarget = screen.inputMap.getTargetForEvent(event);
					
					//	simple current target pointer?  use that.
					if (!directTarget && screen.targets)
					{
						//var index = event.inputIndex;
						//if (!index)
						//	index = 0;
						var index = 0;	//	TODO:  get user index from somewhere
						directTarget = screen.targets[index];
					}
				}
				
				if (directTarget)
					result = directTarget.handleEvent(event);
				else	//	no direct target - just send to screen
					result = screen.handleEvent(event);
				
				if (result)	//	handled?  stop looking.
					break;

				if(wasModal)	//	stop if we hit a modal screen
				{
					//	support clicking away from popup to dismiss
					if((event.eventType === 'mousedown' || event.eventType === 'touchstart') &&
						screen.allowClickAway && !rat.collision2D.pointInRect(event.pos, screen.getBounds()))
					{
						if(screen.clickAwaySound && rat.audio)
							rat.audio.playSound(screen.clickAwaySound);
						rat.screenManager.popScreen();
						result = true;	//	handled, in the form of a close
					}

					break;	//	done handling
				}
			}
			return result;
		},

		updateScreens: function ()
		{
			rat.ui.updateCallCount = 0;	//	debug - count total update calls
			var screen;
			for (var i = rat.screenManager.screenStack.length-1; i >= 0; --i)
			{
				screen = rat.screenManager.screenStack[i];
				if (!screen)
					continue;
				if (screen.isSuspended && !screen.forceScreenActive)
					break;
				rat.profiler.pushPerfMark(screen._screenTag || "???");
				rat.screenManager.screenStack[i].update(rat.deltaTime);
				rat.profiler.popPerfMark(screen._screenTag || "???");
			}
		},

		//
		//	draw all screens in our stack
		//
		drawScreens: function ()
		{

			//	STT rewriting this... now...  Let's get rid of this separation of root and postdraw and stack...
			//	we can move postdraw functionality to a per-screen thing, if needed.
			/*
			if (rat.screenManager.uiRoot != null)
			{
				rat.screenManager.uiRoot.draw();
				rat.screenManager.drawTooltips(rat.screenManager.uiRoot);
			}

			//	need a way better system for handling this post-draw thing.
			if (typeof rat.postUIDraw !== 'undefined')
				rat.postUIDraw();
			*/

			//	draw all screens bottom up
			var i;
			var screen;
			var stack = rat.screenManager.screenStack;
			for(i = 0; i < stack.length; i++)
			{
				screen = stack[i];
				if( screen.isSuspended && !screen.forceScreenActive )
					continue;
				
				rat.profiler.pushPerfMark(screen._screenTag || "???");
				
				//console.log("> screen " + i);
				screen.draw();
				//	only draw tooltips for topmost screen, by default.
				//	if you need something different (e.g. a tooltip up when there's some higher screen?)
				//	then you'll need to add flags to change the default behavior, like
				//	screen.alwaysDrawTooltips or something.
				if (i === stack.length-1)
					rat.screenManager.drawTooltips(screen);
				
				rat.profiler.popPerfMark(screen._screenTag || "???");
				//	post-draw?
			}
		},

		//
		//	Draw the current tooltip, if any, for this screen for this drawing frame.
		//	This list gets cleared every frame, and active elements set up new tooltips to draw as needed.
		//	(this puts tooltips under control of elements, so they can figure out their own location, visible state, etc.,
		//	and so it's easy to clean up when an element dies.)
		//	We draw here so that it happens after all other stuff draws, so tooltips show up on top of everything.
		//
		drawTooltips: function (screen)
		{
			if(screen.activeToolTip)
			{
				//	convert global to screen space (tooltips are always given here in global space)
				//	Why add screen position here?  these positions are already global!
				//screen.activeToolTip.place.pos.x += screen.place.pos.x;
				//screen.activeToolTip.place.pos.y += screen.place.pos.y;

				//	fix up (constrain) location if it's off our total canvas space
				//	todo: configurable buffer space away from edges.
				//	todo: this should happen when position is first calculated, not here at draw time!
				var rect = new rat.shapes.Rect(0, 0, rat.graphics.SCREEN_WIDTH - screen.activeToolTip.size.x, rat.graphics.SCREEN_HEIGHT - screen.activeToolTip.size.y);
				screen.activeToolTip.place.pos.limitToRect(rect);

				//	draw
				screen.activeToolTip.setVisible(true);
				screen.activeToolTip.draw();
				//	and clear for next frame
				screen.activeToolTip = null;
			}
		},

		//	Fired when we get a resize from the graphics system.
		//	This will attempt to call onWindowResize for any active (not deflated) screens
		//	Important:  This is called after the rat graphics system has reacted to resize,
		//	so, depending on your graphics settings, rat.graphics.SCREEN_WIDTH/HEIGHT will be updated correctly.
		onWindowResize: function()
		{
			var curScreen;
			for (var index = rat.screenManager.screenStack.length - 1; index >= 0; --index)
			{
				curScreen = rat.screenManager.screenStack[index];
				if (curScreen.isSuspended && !curScreen.forceScreenActive)
					break;
				if (curScreen.onWindowResize)
					curScreen.onWindowResize();
			}
		},
		
		//	Walk through screen stack, centering all screens based on their stated size and rat's understanding of screen size.
		centerAllScreens: function()
		{
			var curScreen;
			for (var index = rat.screenManager.screenStack.length - 1; index >= 0; --index)
			{
				curScreen = rat.screenManager.screenStack[index];
				curScreen.centerInDisplay();
			}
		}
	};

	rat.addEventListener("resize", rat.screenManager.onWindowResize);
	
	//	Always have the screen manager.
	rat.input.registerEventHandler(rat.screenManager.dispatchEvent);

	//	aliases for convenience with old code
	// rat.graphics.setUIRoot = rat.screenManager.setUIRoot;
	// rat.graphics.getTopScreen = rat.screenManager.getTopScreen;
	// rat.graphics.pushScreen = rat.screenManager.pushScreen;
	// rat.graphics.popScreen = rat.screenManager.popScreen;
	// rat.graphics.doConfirmDialog = rat.screenManager.doConfirmDialog;

});
//--------------------------------------------------------------------------------------------------
//	shape Element
//
//	A rat ui element that supports drawing simple colored shapes with basic canvas calls.
//
//	TODO:  Offscreen and Dirty support
//
rat.modules.add( "rat.ui.r_ui_shape",
[
	{name: "rat.ui.r_ui", processBefore: true },
	{name: "rat.utils.r_utils", processBefore: true },
	"rat.debug.r_console",
	"rat.ui.r_ui_data",
	"rat.graphics.r_graphics",
	"rat.math.r_math",
], 
function(rat)
{
	/**
	 * @constructor
	 * @extends rat.ui.Element
	 * @param {Object} shapeType (one of rat.ui.noShape, rat.ui.circleShape, rat.ui.squareShape, or rat.ui.pathShape)
	*/
	rat.ui.Shape = function (arg1, arg2)
	{
		var parentPane = arg1;
		var shapeType = arg2;
		if (typeof(arg1) === 'number')
		{
			parentPane = null;
			shapeType = arg1;
		}
		
		rat.ui.Shape.prototype.parentConstructor.call(this, parentPane); //	default init
		//	for now, we're going to put all shape types in a single Element class rather than make
		//	a bunch of classes.
		if(shapeType === void 0)
			this.shapeType = rat.ui.circleShape;
		else
			this.shapeType = shapeType;
		//	shapes are often used as containers, so don't turn off mouse tracking
		
		this.cornerRadius = 5;	//	client will presumably change this.
		
		this.strokeWidth = -1;
		this.strokeColor = null;
	};
	rat.utils.inheritClassFrom(rat.ui.Shape, rat.ui.Element);
	rat.ui.Shape.prototype.elementType = 'shape';

	rat.ui.noShape = 0;
	rat.ui.circleShape = 1;
	rat.ui.squareShape = 2;
	rat.ui.roundRectShape = 3;
	rat.ui.pathShape = 4;
	
	rat.ui.Shape.prototype.setShapeType = function (arg)
	{
		if (arg != this.shapeType)
			this.setDirty(true);
		
		this.shapeType = arg;
	};
	rat.ui.Shape.prototype.setCornerRadius = function (arg)
	{
		if (arg < 0)	//	negative corner radius not OK
			arg = 0;
		if (arg != this.cornerRadius)
			this.setDirty(true);
		
		this.cornerRadius = arg;
	};
	
	rat.ui.Shape.prototype.setStroke = function (width, color)
	{
		if (width !== this.strokeWidth || color != this.strokeColor)
			this.setDirty(true);

		this.strokeWidth = width;
		this.strokeColor = color;
	};
	
	rat.ui.Shape.prototype.drawSelf = function ()
	{
		//this.prototype.parentClass.prototype.drawSelf.call or whatever();	//	inherited draw self, if it were needed...
		var ctx = rat.graphics.getContext();
		
		//	define proper ctx path
		if (this.shapeType === rat.ui.circleShape)
		{
			ctx.beginPath();
			var radius = rat.math.min(this.size.x, this.size.y)/2;
			
			var cx = this.size.x/2 - this.center.x;
			var cy = this.size.y/2 - this.center.y;
			ctx.arc(cx, cy, radius, 0, Math.PI * 2, true);
			
			ctx.closePath();
				
		} else if (this.shapeType === rat.ui.squareShape)
		{
			ctx.beginPath();
			ctx.rect(0 - this.center.x, 0 - this.center.y, this.size.x, this.size.y);
			ctx.closePath();
			
		} else if (this.shapeType === rat.ui.roundRectShape)
		{
			rat.graphics.roundRect(
				{x : 0-this.center.x, y : 0-this.center.y, w : this.size.x, h : this.size.y},
				this.cornerRadius
			);
			
		} else
		{
			//	todo: path - maybe with anon function set from outside?
			return;
		}
		
		//	then fill
		ctx.fillStyle = this.color.toString();
		ctx.fill();
		
		//	and optionally stroke
		if (this.strokeWidth && this.strokeColor)
		{
			ctx.lineWidth = this.strokeWidth;
			ctx.strokeStyle = this.strokeColor.toString();
			ctx.stroke();
		}
			
	};

	//	editor properties
	rat.ui.Shape.editProperties = [
		{ label: "shape",
			props: [
				{propName:'shapeType', type:'integer'},	//	todo: pick from list
				{propName:'cornerRadius', type:'float'},	//	todo: pick from list
				{label: 'strokeWidth', propName:'strokeWidth', type:'float'},
				{label: 'strokeColor', propName:'strokeColor', type:'color'},
			],
		}
	];

	rat.ui.Shape.setupFromData = function (pane, data, parentBounds)
	{
		rat.ui.data.callParentSetupFromData(rat.ui.Shape, pane, data, parentBounds);

		if (data.shapeType !== void 0)
			pane.shapeType = data.shapeType;
		if (data.cornerRadius !== void 0)
			pane.setCornerRadius(data.cornerRadius);
		if (data.strokeWidth !== void 0)
			pane.strokeWidth = data.strokeWidth;
		if (data.strokeColor !== void 0)
			pane.strokeColor = data.strokeColor;
	};
	
});
//--------------------------------------------------------------------------------------------------
//	textbox ui element
//	renders with built-in text/font rendering support in canvas
//	(not editable text)
//
//	TODO:  It might be cleaner to use this.lines everywhere, instead of this.value.
//		In too many places we switch between them...
//
//	TODO:  specialDraw system:
//		A callback system for drawing special characters/images in the middle of text.
//		First, set a call back function (setSpecialDraw())
//		Then, support an embedded special control character (rat.ui.TextBox.specialCharacter) followed by an argument character,
//		and when that pair is reached during drawing, don't draw them - instead call specialDraw() callback with those as arguments.
//		This will all happen during normal draw so that if we're drawing offscreen, this special drawing will also happen offscreen, which is good.
//		We might need to preprocess text looking for these special characters when we do line break processing... we need these special characters
//		to be included correctly in line width measurement, which is tricky.  So, yeah, we might need a second callback like specialMeasure,
//		or have specialDraw take an argument saying whether to actually draw?
//
//		specialDraw gets a single argument object with properties:
//			textBox: the textbox object currently drawing
//			ctx: context to draw to
//			argCode: the special argument character embedded in text after special code
//			x, y: the current drawing position. Is this top left, bottom left, center, what?  can't be center, I guess...
//			doDraw : false if we're just measuring.  See results below.
//			
//		specialDraw returns results in an object with these properties:
//			width: width of drawn image
//			height: height of drawn image
//			dirty: true if we should mark the textbox dirty again, e.g. if the drawing is going to animate every frame
//
//		Note:  If we don't already support explicitly overriding line height, we should, since some special characters will be tall,
//			but it'll be too late to change the height of a line when we've drawn the first few characters and then get to a tall special character.
//
rat.modules.add( "rat.ui.r_ui_textbox",
[
	{name: "rat.ui.r_ui", processBefore: true },
	{name: "rat.utils.r_utils", processBefore: true },
	
	"rat.graphics.r_graphics",
	"rat.utils.r_wordwrap",
	"rat.debug.r_console",
	"rat.ui.r_ui_data",
], 
function(rat)
{
	/**
	 * @constructor
	 * @extends rat.ui.Element
	 * can construct with parent, textvalue or just one, or neither.
	 * first argument is generally expected to be parent
	*/
	rat.ui.TextBox = function (value1, value2)
	{
		//	moving toward a new brighter world where you can pass parent in to rat ui element constructors...
		var initParent;
		var initText;
		if (typeof(value1) === 'string')
			initText = value1;
		else
		{
			initParent = value1;
			initText = value2;
		}
		
		rat.ui.TextBox.prototype.parentConstructor.call(this, initParent); //	default init
		if (initText === void 0)
			this.value = "";
		else
			this.value = initText;

		//	reasonable defaults
		this.font = "Calibri";
		this.fontStyle = "";	//	e.g. "bold"
		this.fontSize = 15;
		this.fontLineSpacing = 2;
		this.fontLineHeight = 16;  //	placeholder like everything else - recalculated when real font is set
		//	fontLineHeight should be ascent + descent but no leading, but it's hard to calculate correctly.  :(
		this.fontDescriptor = "15px Calibri";

		this.strokeWidth = -1;
		this.strokeColor = null;
		
		this.textShadowEnabled = false;
		this.textShadowColor = new rat.graphics.Color( 0, 0, 0, .5 );
		this.textShadowOffset = { 
			x: 1,
			y: 1
		};
		
		//	by default, squish text instead of wrapping.  Little labels are more common than big text boxes.
		this.autoWrap = false;

		//	temp defaults - user usually sets this to what they want
		this.size.x = 100;
		//	maxWidth is a way to override how much of the box gets filled by text.
		//	by default, fill the whole box.
		//	maxWidth is also what controls text squishing, usually.
		this.maxWidth = this.size.x;
		this.overriddenMaxWidth = false;
		this.size.y = this.fontLineHeight;
		
		//	our actual text width, calculated later
		this.textWidth = 0;
		
		//	similarly, in a multi-line solution, track the width of each line
		this.lineWidths = [];

		//	default alignments
		this.align = rat.ui.TextBox.alignLeft;
		this.baseline = rat.ui.TextBox.baselineMiddle;

		//console.log("width " + this.size.x);

		this.name = "<txt>" + this.id + "(" + this.value + ")";

		this.textChanged();	//	this updates multi-lines, and sets content size
		
		this.setTracksMouse(false);	//	no mouse tracking, highlight, tooltip, etc. including subelements.

		//	auto-set my size from content if a flag tells us to?  or just have them call another function....
	};
	rat.utils.inheritClassFrom(rat.ui.TextBox, rat.ui.Element);
	rat.ui.TextBox.prototype.elementType = 'textBox';
	
	rat.ui.TextBox.alignLeft = 0;
	rat.ui.TextBox.alignCenter = 1;
	rat.ui.TextBox.alignRight = 2;

	rat.ui.TextBox.baselineTop = 0;
	rat.ui.TextBox.baselineMiddle = 1;
	rat.ui.TextBox.baselineBottom = 2;
	
	rat.ui.TextBox.fontHeightOverrides = {};

	rat.ui.TextBox.prototype.setTextValue = function (value)
	{
		//console.log("setText " + value);
		var oldValue = this.value;
		this.value = "" + value; // Make sure that it is a string

		if (oldValue !== this.value)
			this.textChanged();
	};
	rat.ui.TextBox.prototype.getTextValue = function (trimmed)
	{
		var text = this.value;
		if (text && trimmed)
			text = text.trim();
		return text;
	};

	rat.ui.TextBox.prototype.translateAndSetTextValue = function (value)
	{
		//	NOTE: We CANNOT call setTextValue because some of our classes override that to call translateAndSetTextValue which would cause infinite recursion
		if (rat.string)
			value = rat.string.getString(value);
		if (value !== this.value)
		{
			this.value = value;
			this.textChanged();
		}
	};
	
	//
	//	class-level utility - set font height override (a multiplier to apply based on the font size)
	//	This is set at the TextBox general level, and any instance of TextBox will look for an override when
	//	its height is being set.  See setFontSize below.
	//	This is useful because sometimes a particular font (especially something fancy downloaded from a font site)
	//	will have weird vertical spacing, and ideally we don't want to have to keep manually setting a lineheight every time,
	//	So this is a way to globally set a multiplier per font once, and all future TextBoxes behave better.
	//	That's the idea, anyway.  :)
	//	Compare and contrast with setLineHeight below.
	rat.ui.TextBox.setFontHeightOverride = function (fontName, multiplier)
	{
		rat.ui.TextBox.fontHeightOverrides[fontName] = multiplier;
	},

	//	mostly internal - rebuild full font descriptor based on individual values
	rat.ui.TextBox.prototype.updateFontDescriptor = function ()
	{
		var oldDescriptor = this.fontDescriptor;
		this.fontDescriptor = ("" + this.fontStyle + " " + this.fontSize + "px " + this.font).trim();
		/** @todo	px vs. pt NOT interchangeable!  Switching back to px.  I vaguely remember some browsers not supporting px - was I imagining it?
		   @todo	autocenter if flagged to?
 */

		//	re-measure if needed, e.g. if we have text at all.
		//	for one thing, wrapping code needs to know size of text,
		//	and also this updates our contentSize variable,
		//	which some outside systems (e.g. tooltips) depend on being accurate.
		if (oldDescriptor !== this.fontDescriptor && this.value && this.value.length > 0)
			this.textChanged();
	};

	/**
	 * set font (just font name, or optionally size and style as well)
	 * @param {string} font
	 * @param {number=} size
	 * @param {string=} style
	 */
	rat.ui.TextBox.prototype.setFont = function (font, size, style)
	{
		this.font = font;
		if(size)
			this.setFontSize(size);
		if(style)
			this.setFontStyle(style);
		this.updateFontDescriptor();
	};

	/**
	* set font style only
	*/
	rat.ui.TextBox.prototype.setFontStyle = function (fontStyle)
	{
		this.fontStyle = fontStyle;
		this.updateFontDescriptor();
	};

	/**
	* set font size, which also resets lineheight
	*/
	rat.ui.TextBox.prototype.setFontSize = function (fontSize)
	{
		this.fontSize = fontSize;
		//	check if this is a registered font with special line height override.  See above.
		var mult = 0;
		if (this.font)
			mult = rat.ui.TextBox.fontHeightOverrides[this.font];
		if (mult)
			this.fontLineHeight = (fontSize * mult)|0;	//	nice even value? do we care?
		else
			this.fontLineHeight = fontSize + 1;	//	not sure how to get this accurately - evidently it's a problem
		this.updateFontDescriptor();
	};
	
	/**
	* Set line height explicitly, overriding whatever was calculated in setFontSize.
	* This assumes this function is called after setFontSize (or setFont).
	*/
	rat.ui.TextBox.prototype.setLineHeight = function (lineHeight)
	{
		this.fontLineHeight = lineHeight;
	};

	//	set autowrap on or off
	rat.ui.TextBox.prototype.setAutoWrap = function (autoWrap)
	{
		var oldWrap = this.autoWrap;
		this.autoWrap = autoWrap;
		if (oldWrap !== autoWrap)
			this.textChanged();	//	rewrap with new setting
	};
	
	//	override max width (which is normally calculated from our size when it changes)
	rat.ui.TextBox.prototype.overrideMaxWidth = function (newMaxWidth)
	{
		if (newMaxWidth == -1)
			newMaxWidth = 9999;//Number.MAX_VALUE;	//	ie doesn't like MAX_VALUE here.  :)  Just pick a big one.
		this.maxWidth = newMaxWidth;
		this.overriddenMaxWidth = true;	//	remember in case our size changes - we probably still want to reapply it.
	};

	//	todo: why is this different from autoCenter?  Are we giving up on autoCenter?  It's confusing...
	//	does it mean auto-center CONTENT within our bounds, or does it mean change our bounds?
	//	probably rework that whole system and rename functions to something like
	//	autoCenterContent and centerInParent or whatever.  Does that autocenter children, too?

	//	center text
	rat.ui.TextBox.prototype.centerText = function ()
	{
		//console.log("center text");
		this.align = rat.ui.TextBox.alignCenter;
		this.baseline = rat.ui.TextBox.baselineMiddle;
	};
	
	//	left align text (convenience function that does the same as calling SetAlign)
	rat.ui.TextBox.prototype.leftAlignText = function ()
	{
		this.setAlign(rat.ui.TextBox.alignLeft);
	};
	//	right align text (convenience function that does the same as calling SetAlign)
	rat.ui.TextBox.prototype.rightAlignText = function ()
	{
		this.setAlign(rat.ui.TextBox.alignRight);
	};

	rat.ui.TextBox.prototype.setAlign = function (align)
	{
		if (typeof(align) === 'string')
		{
			if (align === 'left') align = rat.ui.TextBox.alignLeft;
			else if (align === 'center') align = rat.ui.TextBox.alignCenter;
			else if (align === 'right') align = rat.ui.TextBox.alignRight;
		}
		
		if (align !== this.align)
			this.setDirty(true);
		this.align = align;
		
		//	note: we assume that doesn't affect wrapping or squish, so don't need to call textChanged()
	};

	rat.ui.TextBox.prototype.setBaseline = function (baseline)
	{
		//	support alternative arg types (simple text like 'top') for convenience...
		if (typeof(baseline) === 'string')
		{
			if (baseline === 'top') baseline = rat.ui.TextBox.baselineTop;
			else if (baseline === 'middle') baseline = rat.ui.TextBox.baselineMiddle;
			else if (baseline === 'bottom') baseline = rat.ui.TextBox.baselineBottom;
		}
		
		if (baseline !== this.baseline)
			this.setDirty(true);
		this.baseline = baseline;
		
		//	note: we assume that doesn't affect wrapping or squish, so don't need to call textChanged()
	};

	rat.ui.TextBox.prototype.setStroke = function (width, color, doCleanup)
	{
		if (width && color === void 0 && doCleanup === void 0 && typeof(width) !== "number")
		{
			doCleanup = width.doCleanup;
			color = width.color;
			width = width.lineWidth || width.width;
		}

		if (doCleanup === void 0)
			doCleanup = false;

		if (width !== this.strokeWidth || this.strokeCleanup !== doCleanup)
			this.setDirty(true);

		if (this.strokeColor !== color || (this.strokeColor && color && this.strokeColor.equal && !this.strokeColor.equal(color)))
			this.setDirty(true);

		this.strokeWidth = width;
		this.strokeColor = color;
		this.strokeCleanup = doCleanup;
	};
	
	rat.ui.TextBox.prototype.setShadowEnabled = function( enable )
	{
		if (enable === void 0)
			enable = true;
		else
			enable = !!enable;
		if (this.textShadowEnabled !== enable)
		{
			this.textShadowEnabled = enable;
			this.setDirty(true);
		}
	};
	
	//	Set if we should be using a shadow
	rat.ui.TextBox.prototype.setShadow = function( color, offsetX, offsetY )
	{
		var changed = false;
		if ( color && !this.textShadowColor.equal(color) )
		{
			changed = true;
			this.textShadowColor.copyFrom( color );
		}
		
		if (offsetX !== void 0 && offsetX !== this.textShadowOffset.x)
		{
			changed = true;
			this.textShadowOffset.x = offsetX;
		}
		if (offsetY !== void 0 && offsetY !== this.textShadowOffset.y)
		{
			changed = true;
			this.textShadowOffset.y = offsetY;
		}
		
		if (changed && this.textShadowEnabled)
			this.setDirty(true);
	};
	
	//	return correct X positioning for text, based on desired alignment.
	//	and set render context to use that alignment
	rat.ui.TextBox.prototype.setupAlignX = function (ctx, lineWidth)
	{		
		var x = 0;
		
		//	Note:  Stroked text technically draws outside the space given.  This is not ideal.  See comment below in drawLine().
		//	We make a "maxWidth" adjustment below.  We need a tiny left/right adjustment here, as well, depending on alignment.
		//	e.g. if text is left-aligned, bump in a tiny bit so the stroke doesn't go outside our bounds!
		
		//	Note:  Instead of using context's textAlign, we could do the math ourselves and always use left or center or something.
		//	But this is working fine.
		
		//	OK, actually, it's useful in really obscure cases (like typewriter text) to force left alignment,
		//	and do the math ourselves.  Let's support that here.
		if (this.forceLeftRender && lineWidth)
		{
			ctx.textAlign = "left";
			if (this.align === rat.ui.TextBox.alignLeft)
				x = 0 + this.strokeWidth / 2;
			else if(this.align === rat.ui.TextBox.alignCenter)
				x = this.size.x / 2 - lineWidth/2 + this.strokeWidth / 2;
			else
				x = this.size.x - lineWidth;// + this.strokeWidth / 2;
			
		} else {
			if(this.align === rat.ui.TextBox.alignLeft)
			{
				ctx.textAlign = "left";
				x = 0 + this.strokeWidth / 2;
			} else if(this.align === rat.ui.TextBox.alignCenter)
			{
				ctx.textAlign = "center";
				x = this.size.x / 2;
			} else
			{
				ctx.textAlign = "right";
				x = this.size.x - this.strokeWidth / 2;
			}
		}
		
		if (!this.ignoreCenter)	//	 a little hacky...  see buildOffscreen
			x -= this.center.x;

		return x;
	};

	rat.ui.TextBox.prototype.setupAlignY = function (ctx)
	{
		var y = 0;
		if(this.baseline === rat.ui.TextBox.baselineTop)
		{
			ctx.textBaseline = "top";
			y = 0;
		} else if(this.baseline === rat.ui.TextBox.baselineMiddle)
		{
			ctx.textBaseline = "middle";
			y = this.size.y / 2;
		} else
		{
			ctx.textBaseline = "bottom";
			y = this.size.y;
		}
		if (!this.ignoreCenter)	//	 a little hacky...  see buildOffscreen
			y -= this.center.y;
		return y;
	};
	
	//
	//	our text has changed.
	//	do some preflighting - figure out if we have more than one line to draw.
	//	also, a good place for optional rendering optimization.
	//
	rat.ui.TextBox.prototype.textChanged = function ()
	{
		this.setDirty();
		
		if(!this.value || this.value.length <= 0 || !rat.graphics.ctx)
		{
			this.lines = [];
			this.lineWidths[0] = 0;
			this.textWidth = 0;
			this.setContentSize(0, 0);
			return;
		}

		//	for any measuring that needs to happen, make sure we've got the right font set up in the ctx
		rat.graphics.ctx.font = this.fontDescriptor; //	for measuring
		
		//	In some circumstances, we're going to do this repeatedly until things fit...
		//	but let's limit that.
		var totalHeight = this.fontLineHeight;
		var squishForHeight = 1;
		var fitVertically = false;
		for (var vFitIndex = 0; !fitVertically && vFitIndex < 4; vFitIndex++)
		{
			//	if autowrap is set, do that.
			if(this.autoWrap)
			{
				this.lines = rat.wordwrap.wrapString(this.value, this.maxWidth * squishForHeight, rat.graphics.ctx);
			} else
			{	//	otherwise, check for manual wrapping
				//	could use string.split() here, but I want to maybe handle things differently - clean up whitespace? handle tab indents?
				this.lines = this.value.split('\n');
			}

			//	some problem?
			if(!this.lines || this.lines.length < 1)	//	can this even happen?
			{
				this.lines = [];
				this.setContentSize(0, 0);
				return;
			}
			
			//	see if we fit vertically.
			//	TODO:  Factor in vertical alignment!  I think it's significant.  This might currently assume baselineMiddle?
			//	Note that we say a single line always fits, no matter what our bounds say. We can't get shorter than one line.
			totalHeight = this.fontLineHeight * this.lines.length + this.fontLineSpacing * (this.lines.length - 1);
			if (this.lines.length > 1 && totalHeight > this.size.y)	//	too big
				squishForHeight += 0.1;	//	allow more text in each line, each time through
			else
				fitVertically = true;
		}

		//	measure us...
		var widest = 0;
		rat.graphics.ctx.font = this.fontDescriptor; //	for measuring
		for (var i = 0; i < this.lines.length; i++)
		{
			try
			{
				var metrics = rat.graphics.ctx.measureText(this.lines[i]);
				if (metrics.width > widest)
					widest = metrics.width;
				this.lineWidths[i] = metrics.width;
			}
			catch(err)
			{
				rat.console.log("r_ui_textbox error: " + err.message);
			}
		}

		if (this.lines.length === 1)	//	single line
		{
			this.lines = [];	//	just use this.value instead of array
			this.setContentSize(widest, this.fontLineHeight);
		} else
		{
			this.setContentSize(widest, totalHeight);
		}
		
		//	remember how wide our widest line was, in case somebody else wants it.
		this.textWidth = widest;
	};
	
	//	get actual drawing width of my current text, as calculated when it was set.
	//	We factor in this.maxWidth here, so the caller is for sure finding out how wide the text will be
	//	when it is drawn!
	rat.ui.TextBox.prototype.getTextWidth = function()
	{
		if (this.maxWidth < this.textWidth)
			return this.maxWidth;
		else
			return this.textWidth;
	};
	
	//	todo: getLineWidths()
	
	//	get actual height of my text
	//	This may not be super accurate, but we try.
	rat.ui.TextBox.prototype.getTextHeight = function()
	{
		var lineCount = this.lines.length || 1;
		totalHeight = this.fontLineHeight * lineCount + this.fontLineSpacing * (lineCount - 1);
		return totalHeight;
	};
	
	//	util to draw a single line of text
	//	useful to subclasses.  This function assumes x and y have been correctly calculated based on alignment, multi-lines, etc.
	rat.ui.TextBox.prototype.drawLine = function (ctx, x, y, text)
	{
		//console.log("tsize " + this.size.x + ", " + this.size.y);
		//ctx.strokeStyle = "FF40F0";
		//ctx.strokeRect(-this.center.x, -this.center.y, this.size.x, this.size.y);

		if(!text)
			return;

		ctx.font = this.fontDescriptor;

		// this was previously using color.toString(), which prevents us from setting gradients as fillstyles, 
		//	also color does appear to work even though its a rat-object, possibly because its already listed in distinct 'rgba' fields
		// TODO - redo this fillStyle to take a rat FillStyle class that can have color or gradient or pattern as defined by 
		//			the example here - http://www.w3schools.com/tags/canvas_fillstyle.asp
		// TODO ALSO: along the same lines we'd want to fix things like button and fillbar that also use color instead of style
		// FIXME ! hackety foo for now! boo! :(
		if (this.color.a)
			ctx.fillStyle = this.color.toString();		// if it has a rat.Color alpha field, assume it's a color
		else
			ctx.fillStyle = this.color;					// otherwise assume its not a rat color object and that it may be a proper style object instead

		var maxWidth = this.maxWidth;
		if (this.strokeWidth > 0 && this.strokeColor)
		{
			//	stroked text technically goes outside "maxWidth", which is undesirable in 2 ways.
			//	1: it means text is going outside carefully measured spaces, by a few pixels.
			//	2: it means if we have text offscreen buffer rendering enabled, the stroked edges get clipped off.
			//	A solid way to correct this, then, is to use a narrower maxWidth when there's a stroke involved.
			//	This means stroked text will be slightly more squished than non-stroked text, if squishing is happening at all,
			//	but we judge that to be correct behavior.
			//	And note that this maxWidth adjustment here means all text below uses this new adjusted maxWidth;
			//	(adjust our width by half the stroke, on both left and right, so whole stroke.)
			maxWidth -= this.strokeWidth;
		
			ctx.strokeStyle = this.strokeColor.toString();
			ctx.lineWidth = this.strokeWidth;
			ctx.strokeText(text, x, y, maxWidth);
		}
		
		//	Shadow support
		if (this.textShadownEnabled &&
			this.textShadowColor.a > 0 &&
			this.textShadowOffset.x !== 0 && 
			this.textShadowOffset.y !== 0 )
		{
			ctx.shadowColor = this.textShadowColor.toString();
			ctx.shadowOffsetX = this.textShadowOffset.x;
			ctx.shadowOffsetY = this.textShadowOffset.y;
		}

		ctx.fillText(text, x, y, maxWidth);

		//	if we're doing a stroke, do a thin stroke inside as well - it cleans up the edges of the normal text rendering
		if (this.strokeCleanup && this.strokeWidth >= 0 && this.strokeColor)
		{
			var tempColor = new rat.graphics.Color(this.strokeColor);
			tempColor.a = 0.3;

			ctx.strokeStyle = tempColor.toString();
			ctx.lineWidth = 1;
			ctx.strokeText(text, x, y, maxWidth);
		}
	};

	rat.ui.TextBox.prototype.drawSelf = function ()
	{
		var ctx = rat.graphics.getContext();
		
		var x, y;
		if(this.lines.length > 0)	//	multi-line version
		{
			y = this.setupAlignY(ctx);
			var lineHeight = this.fontLineHeight + this.fontLineSpacing;	//	font size plus padding - don't have an easy way to get text height
			var height = this.lines.length * lineHeight;

			if(this.baseline === rat.ui.TextBox.baselineMiddle)
			{
				y -= (height - lineHeight) / 2;	//	text is already aligned middle, so only move up for lines beyond first
			} else if(this.baseline === rat.ui.TextBox.baselineBottom)
			{
				y -= (height - lineHeight);	//	text is aligned to bottom, so only go up a line for each additional line beyond first
			}

			for(var i = 0; i < this.lines.length; i++)
			{
				x = this.setupAlignX(ctx, this.lineWidths[i]);
				this.drawLine(ctx, x, y, this.lines[i]);
				y += lineHeight;
			}

		} else
		{	//	simple single-line version
			x = this.setupAlignX(ctx, this.textWidth);
			y = this.setupAlignY(ctx);

			this.drawLine(ctx, x, y, this.value);
		}
	};

	rat.ui.TextBox.prototype.boundsChanged = function ()
	{
		if (!this.overriddenMaxWidth)
			this.maxWidth = this.size.x;	//	auto-apply new size as our new max width for drawing
		
		rat.ui.TextBox.prototype.parentPrototype.boundsChanged.call(this);	//	inherited normal func

		//	TODO:  I would like to not do either of these if our SIZE didn't change, but I don't have a way to know.
		//	should probably pass in old bounds to this function!
		
		this.textChanged();	//	rewrap/squish text with new setting
		this.setDirty(true);
	};

	// Support for creation from data
	//
	//text: ""
	//font:{
	//	font:"",
	//	size: 00,
	//	style"",
	//	stroke:{
	//		width:
	//		color:{
	//			r:0
	//			g:0
	//			b:0,
	//			a:0
	//		}
	//	},
	//	align:"",
	//	baseline:""
	//}
	//
	
	//	properties which are editable in the ui editor,
	//	and will be understood by setupFromData().
	//	This list is located here for convenient editing along with the functionality of this module,
	//	and this setupFromData call.
	//
	//	Ideally, when you are adding supported properties to this class,
	//	add them here, too, so they can easily be edited in the editor!
	//
	
	//	some standard reused groups of props...
	rat.ui.TextBox.standardFontEditProperties =
	{
		label: "font",
		props: [
			{label: 'font', propName:'font.font', type:'string', defValue : 'impact'},
			{label: 'size', propName:'font.size', type:'float', defValue : 40},
			{label: 'style', propName:'font.style', type:'string'},
			{label: 'lineHeight', propName:'font.lineHeight', type:'float'},
		],
	};
	rat.ui.TextBox.standardFontStrokeEditProperties =
	{ label: "stroke",
		props: [
			{label: 'width', propName:'font.stroke.width', type:'float'},
			{label: 'color', propName:'font.stroke.color', type:'color'},
			{label: 'cleanup', propName:'font.stroke.doCleanup', type:'boolean', tipText:"expensive way to try to make edges nicer"},
		],
	};
	
	//	the list for textbox
	rat.ui.TextBox.editProperties = [
		{ label: "text",
			props: [
				{propName:'text', type:'string'},
				{propName:'translateText', type:'string', tipText:"overrides text, tries to translate first"},
				{propName:'autoWrap', type:'boolean'},
			],
		},
		
		rat.ui.TextBox.standardFontEditProperties,	//	include the above, but in a flexible way (so other classes can use it, too)
		
		{ label: "align",
			props: [
				//	todo: change to combo box with values
				{propName:'align', type:'string', defValue : 'left'},
				{propName:'baseline', type:'string', defValue : 'middle', tipText:"vertical alignment"},
			],
		},
		
		rat.ui.TextBox.standardFontStrokeEditProperties,	//	holy property name, batman...
	];
	
	rat.ui.TextBox.setupFromData = function (pane, data, parentBounds)
	{
		rat.ui.data.callParentSetupFromData( rat.ui.TextBox, pane, data, parentBounds );

		//	note: as with all setupFromData functions, keep in mind that we may be modifying
		//	an existing textbox or setting props for a new one
		//	so don't ignore things like empty style strings - we set them anyway!
		
		//	todo: generalize this to read from editProperties above
		if (data.font)
		{
			if (data.font.font != void 0)
				pane.setFont(data.font.font);
			if (data.font.size != void 0)
				pane.setFontSize( data.font.size );
			if (data.font.style != void 0)
				pane.setFontStyle(data.font.style);
			if (data.font.lineHeight)	//	zero is not valid, so skip that, too.
				pane.setLineHeight(data.font.lineHeight);
			if (data.font.stroke != void 0)
			{
				pane.setStroke(data.font.stroke.width, rat.graphics.fixColorStyle(data.font.stroke.color), data.font.stroke.doCleanup);
			}
		}
		if (data.align === "left" )
			pane.setAlign(rat.ui.TextBox.alignLeft);
		else if (data.align === "center" )
			pane.setAlign(rat.ui.TextBox.alignCenter);
		else if (data.align === "right" )
			pane.setAlign(rat.ui.TextBox.alignRight);

		if (data.baseline === "top" )
			pane.setBaseline(rat.ui.TextBox.baselineTop);
		else if (data.baseline === "middle" || data.baseline === "center")
			pane.setBaseline(rat.ui.TextBox.baselineMiddle);
		else if (data.baseline === "bottom" )
			pane.setBaseline(rat.ui.TextBox.baselineBottom);

		if (data.autoWrap !== void 0)
			pane.setAutoWrap(!!data.autoWrap);
		if (data.translateText)
			pane.translateAndSetTextValue(data.translateText || "");
		else
			pane.setTextValue(data.text || "");
		
	};

	/** 
	    Special version of textbox that default to localizing text
	    @constructor
	    @extends rat.ui.TextBox
	   
 */
	rat.ui.TranslatedTextBox = function (value)
	{
		if (rat.string)
			value = rat.string.getString(value);
		rat.ui.TranslatedTextBox.prototype.parentConstructor.call(this, value); //	default init
	};
	rat.utils.inheritClassFrom(rat.ui.TranslatedTextBox, rat.ui.TextBox);

	/*
	 * Set the value with the translated version of the text
	 */
	rat.ui.TranslatedTextBox.prototype.setTextValue = function (value)
	{
		rat.ui.TranslatedTextBox.prototype.parentPrototype.translateAndSetTextValue.call(this, value);
	};

	/*
	 * Bypass the translation step
	 */
	rat.ui.TranslatedTextBox.prototype.setTextValueRaw = function (value)
	{
		rat.ui.TranslatedTextBox.prototype.parentPrototype.setTextValue.call(this, value);
	};

});
//--------------------------------------------------------------------------------------------------
//	button Element

//	TODO
//		Improved color state setting functions for color, text, and images.
//		support overlay art?  A second image that shows/doesn't show also based on state.  could be flexible and generic.
//
//		Figure out how to autosize a spritebutton from an image the loads late

//	Notes on Dirty Flags for buttons.
//		First of all, buttons contain images and text,
//			but they aren't classic "subElement" entries.
//			So, one thing we do is let the image or text track its own dirty state like normal
//			(which is useful because they do that carefully on each function)
//			and then we do a special dirty check ourselves by supplying a checkDirty() function, which rat.ui.Element respects.
//		Second, it's tricky because we might have several buttons that could be changing or updating, or whatever,
//			or we might even have images/text that is NOT changing, but we swap out which one is active when our state changes.
//			State changes need to potentially set dirty flag, even if our text or images haven't themselves changed.
//			We solve this with the simple use of flagsThatDirtyMe, which is what it's for.  :)
//		Also note that updateDisplayState is a bad place to check/set dirty, because it's called from drawSelf() which is
//			only called after the rat.ui.Element system has already done its dirty check.
//			So, that's not quite ideal.  the updateDisplayState call should probably get moved to earlier (e.g. draw() override)
//			or happen on state changes instead of in draw loop.
//			But for now, I'm not too worried.  Our other dirty checks should cover it.
//		Also: buttons are not necessarily an ideal place to be using offscreens, since they update often?
//			You might consider setting textUseOffscreen to make the actual text offscreen, which is generally a good idea with text,
//			but leave the button not offscreened?  It's up to you and your specific case, of course.
//
rat.modules.add( "rat.ui.r_ui_button",
[
	{name: "rat.utils.r_utils", processBefore: true },
	{name: "rat.ui.r_ui", processBefore: true },
	{name: "rat.ui.r_ui_textbox", processBefore: true },
	
	"rat.ui.r_ui_bubblebox",
	"rat.ui.r_ui_sprite",
	"rat.graphics.r_image",
	"rat.graphics.r_graphics",
],
function(rat)
{
	/**
	 * @constructor
	 * @extends rat.ui.Element
	 * @param {string=} resource
	 * @param {string=} resourceHi
	 * @param {string=} resourcePressed
	 * @param {string=} resourceDisabled
	 * @param {Object=} extra1 (optional - used for external image formats (see makeImage))
	 * @param {boolean=} extra2 (optional - used for external image formats (see makeImage))
	*/
	rat.ui.Button = function (buttonType, resource, resourceHi, resourcePressed, resourceDisabled, extra1, extra2)
	{
		rat.ui.Button.prototype.parentConstructor.call(this); //	default init

		this.setTargetable(true);
		this.isSetup = false;
		if (buttonType !== void 0)
			this.setupButton(buttonType, resource, resourceHi, resourcePressed, resourceDisabled, extra1, extra2);
	};
	rat.utils.inheritClassFrom(rat.ui.Button, rat.ui.Element);
	rat.ui.Button.prototype.elementType = "button";

	//	these flag changes need to set me dirty, because they change my look!
	//	see rat.ui module, comments above flagsThatDirtyMe variable,
	//	and rat.ui.Element.prototype.checkFlagsChanged function.
	rat.ui.Button.prototype.flagsThatDirtyMe = 
			rat.ui.Element.highlightedFlag |
			rat.ui.Element.enabledFlag |
			rat.ui.Element.pressedFlag |
			rat.ui.Element.toggledFlag;

	rat.ui.Button.prototype.textInset = 8;	//	how far in from edges text is placed, by default
	//	shift text around, e.g. if some fonts naturally hang low, you can change a button's y offset to compensate
	rat.ui.Button.prototype.textOffsetX = 0;
	rat.ui.Button.prototype.textOffsetY = 0;
	rat.ui.Button.prototype.textOffsetY = 0;
	//	in order to pick a good default and also support explicit setting, track whether the user
	//	has explicitly set a size.
	rat.ui.Button.prototype.useFontSize = false;
	
	//	some standard/class-wide values
	rat.ui.Button.spriteType = 1;
	rat.ui.Button.bubbleType = 2;
	rat.ui.Button.cheapType = 3;
	rat.ui.Button.coolType = 4;	//	an attempt at a very nice-looking but still shape-defined button.
	
	//	this value is used to set default click sound for all future buttons.
	//	maybe should default to undefined.
	rat.ui.Button.defaultClickSound = 'click';
	//rat.ui.Button.defaultFocusSound = void 0;
	
	rat.ui.Button.standardCornerScale = (1/6);	//	use this to come up with standard corner radius from height
	
	/**
	 * @param {string=} resource
	 * @param {string=} resourceHi
	 * @param {string=} resourcePressed
	 * @param {string=} resourceDisabled
	 * @param {Object=} extra1 (optional - used for external image formats (see makeImage))
	 * @param {boolean=} extra2 (optional - used for external image formats (see makeImage))
	*/
	rat.ui.Button.prototype.setupButton = function(buttonType, resource, resourceHi, resourcePressed, resourceDisabled, extra1, extra2)
	{
		//	support setting up again,
		//	e.g. to use the same button but totally replace its art/settings.
		//	This is mostly useful for setting up from data in places like the editor, where data is going to change a lot,
		//	but we don't delete/recreate the button.
		//	In general, rat ui elements need to handle being RE-set up from data.
		if (this.isSetup)
		{
			//	if needed, refactor to "unsetup" or something.
			this.removeAllSubElements();
			this.buttonImage = null;
			this.imageNormal = null;
			this.imageHi = null;
			this.imagePressed = null;
			this.imageDisabled = null;
			this.text = null;	//	force it to be recreated.
		}
		this.isSetup = true;

		this.type = buttonType;
		
		//	create bubble/sprite resource
		if (buttonType === rat.ui.Button.spriteType)
		{
			this.buttonImage = new rat.ui.Sprite();
			
		} else if (buttonType === rat.ui.Button.bubbleType)
		{
			this.buttonImage = new rat.ui.BubbleBox();
		} else if ((buttonType === rat.ui.Button.cheapType || buttonType === rat.ui.Button.coolType) && resource)
		{
			//	cheap buttons support an image thrown in there as well, if one was provided
			this.buttonImage = new rat.ui.Sprite();
		}
		
		//	if image subelement successfully got created above
		if (this.buttonImage)
		{
			this.appendSubElement(this.buttonImage);
			
			//	copy some relevant flags...
			this.copyContentFlags();
		}
		
		//	we have several frames we explicitly keep track of.
		//	todo: standardize all this state-related rendering stuff.  Have one master array with structs that define
		//	all rendering info (image, colors, text colors, frames, etc.) along with flags for identifying complex state combinations.
		//	Working on this.  see colorStates.  Note that I'm leaving old support in here,
		//	for older projects that still depend on this code.
		
		if (resource)
		{
			this.imageNormal = rat.graphics.makeImage(resource, extra1, extra2);
			//	questionable special case:
			if ((buttonType === rat.ui.Button.spriteType
					|| buttonType === rat.ui.Button.cheapType
					|| buttonType === rat.ui.Button.coolType)
				&& this.imageNormal.size)
			{
				//console.log("trying to set button size from image");
				this.setSize(this.imageNormal.size.x, this.imageNormal.size.y);	//	this will work if the image was already cached...
				//console.log("done trying");
			}
		}
		
		if (resourceHi)
			this.imageHi = rat.graphics.makeImage(resourceHi, extra1, extra2);

		if (resourcePressed)
			this.imagePressed = rat.graphics.makeImage(resourcePressed, extra1, extra2);

		if (resourceDisabled)
			this.imageDisabled = rat.graphics.makeImage(resourceDisabled, extra1, extra2);

		this.toggles = false; //	see rat.ui.Element.toggledFlag - we aren't a toggle button by default

		this.clickSound = rat.ui.Button.defaultClickSound;	//	apply default click sound, if there is one
		//this.focusSound = rat.ui.Button.defaultFocusSound;	//	if there is one

		this.name = "<but>" + this.id;
		
		//	in case this setup happened after our size was set,
		//	then update newly created button image size...
		if (this.buttonImage)
			this.boundsChanged();
		
		//	for cheap/cool, some additional properties
		this.cornerRadius = 10;
		this.cornerRadiusSet = false;	//	we'll adapt this on the fly to size changes, unless somebody sets it explicitly.
		
		this.setDirty(true);
	};

	/**
	* util to make a simple sprite button
	* @param {string=} res
	* @param {string=} resHi
	* @param {string=} resPressed
	* @param {string=} resDisabled
	* @param {Object=} extra1 (optional - used for external image formats (see makeImage))
	* @param {boolean=} extra2 (optional - used for external image formats (see makeImage))
	*/
	rat.ui.makeSpriteButton = function (res, resHi, resPressed, resDisabled, extra1, extra2)
	{
		return new rat.ui.Button(rat.ui.Button.spriteType, res, resHi, resPressed, resDisabled, extra1, extra2);
	};

	/**
	* util to make a simple bubble button
	* @param {string=} resPressed
	* @param {string=} resDisabled
	*/
	rat.ui.makeBubbleButton = function (res, resHi, resPressed, resDisabled)
	{
		return new rat.ui.Button(rat.ui.Button.bubbleType, res, resHi, resPressed, resDisabled);
	};

	//	Create a table of standard color states based on a starting color.
	rat.ui.Button.createStandardColorStates = function(color)
	{
		if (typeof(color) === 'string')
			color = new rat.graphics.Color(color);

		var colorStates = [];
			//	this is a bunch of code to make up colors based on some vaguely mid-range color being passed in.
			//	These are good defaults, and you can override stuff by calling one of the setStateXXX functions below, if you want.
		colorStates[0] = {};
		colorStates[0].flags = rat.ui.Element.enabledFlag; //	normal state
		colorStates[0].color = color.copy();
		colorStates[0].textColor = new rat.graphics.Color(color.r * 4, color.g * 4, color.b * 4, color.a);
		colorStates[0].frameColor = new rat.graphics.Color(color.r / 4, color.g / 4, color.b / 4, color.a);
		colorStates[0].frameWidth = 4;
		
		colorStates[1] = {};
		colorStates[1].flags = rat.ui.Element.enabledFlag | rat.ui.Element.highlightedFlag; //	highlight state
		colorStates[1].color = new rat.graphics.Color(color.r + 50, color.g + 50, color.b + 50, color.a);
		colorStates[1].textColor = new rat.graphics.Color(color.r * 6, color.g * 6, color.b * 6, color.a);
		colorStates[1].frameColor = new rat.graphics.Color(color.r / 5, color.g / 5, color.b / 5, color.a);
		colorStates[1].frameWidth = 4;
		
		colorStates[2] = {};
		colorStates[2].flags = rat.ui.Element.enabledFlag | rat.ui.Element.pressedFlag;
		colorStates[2].color = new rat.graphics.Color(color.r / 2, color.g / 2, color.b / 2, color.a);
		colorStates[2].textColor = new rat.graphics.Color(color.r * 4, color.g * 4, color.b * 4, color.a);
		colorStates[2].frameColor = new rat.graphics.Color(color.r / 5, color.g / 5, color.b / 5, color.a);
		colorStates[2].frameWidth = 4;
		
		colorStates[3] = {};
		colorStates[3].flags = rat.ui.Element.enabledFlag | rat.ui.Element.toggledFlag;
		colorStates[3].color = colorStates[0].color.copy();
		colorStates[3].textColor = new rat.graphics.Color(color.r * 10, color.g * 10, color.b * 10, color.a);
		colorStates[3].frameColor = new rat.graphics.Color(color.r * 4, color.g * 4, color.b * 4, color.a);
		colorStates[3].frameWidth = 4;
		
		colorStates[4] = {};
		colorStates[4].flags = rat.ui.Element.enabledFlag | rat.ui.Element.toggledFlag | rat.ui.Element.highlightedFlag;
		colorStates[4].color = colorStates[1].color.copy();
		colorStates[4].textColor = colorStates[1].textColor.copy();
		colorStates[4].frameColor = new rat.graphics.Color(color.r * 4, color.g * 4, color.b * 4, color.a);
		colorStates[4].frameWidth = 4;
		
		colorStates[5] = {};
		colorStates[5].flags = rat.ui.Element.enabledFlag | rat.ui.Element.toggledFlag | rat.ui.Element.pressedFlag;
		colorStates[5].color = colorStates[2].color.copy();
		colorStates[5].textColor = colorStates[2].textColor.copy();
		colorStates[5].frameColor = new rat.graphics.Color(color.r * 4, color.g * 4, color.b * 4, color.a);
		colorStates[5].frameWidth = 4;
		
		colorStates[6] = {};
		colorStates[6].flags = 0; //	disabled (enabled flag is not set, unlike others)
		colorStates[6].color = new rat.graphics.Color(120, 120, 120, color.a);
		colorStates[6].textColor = new rat.graphics.Color(64, 64, 64, color.a);
		colorStates[6].frameColor = new rat.graphics.Color(64, 64, 64, color.a);
		colorStates[6].frameWidth = 4;
		
		return colorStates;
	}

	//
	//	Make a cheap button - a simple boxy colored button
	//	This makes tons of assumptions, and isn't pretty, but does support many distinct button states.
	//	Useful for prototyping, if nothing else.
	//	I wish I had reversed these arguments, since res is almost always null.
	rat.ui.makeCheapButton = function (res, color)
	{
		var colorStates = rat.ui.Button.createStandardColorStates(color);
		return rat.ui.makeCheapButtonWithColors(res, colorStates);
	};
	
	//	similar, but hopefully a little prettier!
	rat.ui.makeCoolButton = function (res, color)
	{
		var colorStates = rat.ui.Button.createStandardColorStates(color);
		var but = new rat.ui.Button(rat.ui.Button.coolType, res);
		but.colorStates = colorStates;
		return but;
	};
	
	//	make a cheap button, but also specify color states rather than use the defaults.
	//	This is similar to calling makeCheapButton and then setStateColors, I think.
	rat.ui.makeCheapButtonWithColors = function (res, colorStates)
	{
		var button = new rat.ui.Button(rat.ui.Button.cheapType, res);
		button.colorStates = colorStates;

		return button;
	};

	//	special dirty check functionality, since we have image/text subobjects that aren't subelements
	rat.ui.Button.prototype.checkDirty = function()
	{
		if ((this.buttonImage && this.buttonImage.isDirty) || (this.text && this.text.isDirty) || this.isDirty)
			return true;
		return false;
	};

	rat.ui.Button.prototype.setOutline = function (enabled, scale)
	{
		if (this.buttonImage)
			this.buttonImage.setOutline(enabled, scale);
	};
	
	//	copy some relevant flags if I have an image
	rat.ui.Button.prototype.copyContentFlags = function()
	{
		if (this.buttonImage)
		{
			var flagMask = 0
				| rat.ui.Element.autoSizeAfterLoadFlag
				| rat.ui.Element.autoScaleAfterLoadFlag
				| rat.ui.Element.autoCenterAfterLoadFlag
			;
			//	We want these to match exactly, so clear whatever was there first.
			var oldImageFlags = this.buttonImage.flags;
			this.buttonImage.flags &= ~flagMask;
			var newValues = this.flags & flagMask;
			this.buttonImage.flags |= newValues;
			this.buttonImage.checkFlagsChanged(oldImageFlags);
		}
	}
	
	//	(override) set flag - apply some flags to my sub-image, too.
	rat.ui.Button.prototype.setFlag = function (flag, val)
	{
		rat.ui.Button.prototype.parentPrototype.setFlag.call(this, flag, val);	//	do inherited behavior
		
		this.copyContentFlags();
	};

	//	(override) button bounds changed - adjust contents and whatnot accordingly
	rat.ui.Button.prototype.boundsChanged = function ()
	{
		//console.log("button size changed");
		//	make sure our subelements match our size
		//	change this logic depending on type!  (sprite buttons, don't do this)
		if (this.type === rat.ui.Button.bubbleType)
		{
			//console.log("btn: setting image size to " + this.size.x + ", " + this.size.y);
			if (this.buttonImage)
				this.buttonImage.setSize(this.size.x, this.size.y);
			this.setContentSize(this.size.x, this.size.y);
		}
		else if (this.type === rat.ui.Button.spriteType)
		{
			//	Generally, we want our sprite image bounds to match our button size
			if (this.buttonImage)
				this.buttonImage.setSize(this.size.x, this.size.y);
			this.setContentSize(this.size.x, this.size.y);
		}
		else if (this.text)
		{
			//console.log("btn: setting text size to " + this.size.x + ", " + this.size.y);
			this.resizeTextBox();
			//	todo: this isn't called everywhere - and is it right?  shouldn't we only do this if we're ONLY text?  How likely is that?
			this.setContentSize(this.text.contentSize.x, this.text.contentSize.y);
		}
		
		//	if the corner radius hasn't been explicitly set, adapt it!
		if (this.type === rat.ui.Button.coolType && !this.cornerRadiusSet)
		{
			this.cornerRadius = (this.size.y * rat.ui.Button.standardCornerScale)|0;
		}

		rat.ui.Button.prototype.parentPrototype.boundsChanged.call(this);	//	also do inherited behavior
	};

	rat.ui.Button.prototype.centerContent = function ()
	{
		if (this.buttonImage)
		{
			this.buttonImage.centerInParent();
		}
		if (this.text)
		{
			this.text.centerInParent();
		}
	};

	//	if the button is set to not adjust for scale, we need to make sure our bubble box inside knows about it.
	//
	rat.ui.Button.prototype.setAdjustForScale = function (adjust)
	{
		rat.ui.Element.prototype.setAdjustForScale.call(this, adjust);	//	set our flag normally
		if (this.buttonImage)
			this.buttonImage.setAdjustForScale(adjust);
	};

	//	set text inset (how far from edges text is)
	rat.ui.Button.prototype.setTextInset = function(value)
	{
		if (this.textInset != value)
		{
			this.textInset = value;
			this.resizeTextBox();
		}
	};
	
	//	shift text position a little from where it's normally calculated to be,
	//	still respecting inset and centering and whatnot.
	//	This is useful if, for instance, a particular font hangs unusually high or low.
	//	Compare with setTextPosition below, which is absolute and turns off auto centering and ignores.
	rat.ui.Button.prototype.setTextOffset = function(offX, offY)
	{
		if (this.textOffsetX !== offX || this.textOffsetY !== offY)
		{
			this.textOffsetX = offX;
			this.textOffsetY = offY;
			this.resizeTextBox();
		}
	};
	
	//
	//	Place text specifically here.
	//	This means stop centering and put it at this specific location.
	//
	rat.ui.Button.prototype.setTextPosition = function (x, y)
	{
		if (this.text)
		{
			if (this.text.place.pos.x !== x || this.text.place.pos.y !== y)
				this.setDirty(true);
			
			this.text.place.pos.x = x;
			this.text.place.pos.y = y;
			this.text.setAlign(rat.ui.TextBox.alignLeft);
			this.text.setBaseline(rat.ui.TextBox.baselineTop);
		}
	};

	//
	//	get reference to our text box so it can be directly manipulated.
	//
	rat.ui.Button.prototype.getTextBox = function ()
	{
		return this.text;
	};
	
	//	Make sure we have a text box (create it, if needed) and return it.
	rat.ui.Button.prototype.checkAndMakeTextBox = function ()
	{
		if (!this.text)	//	not already built
		{
			//	create text box
			this.text = new rat.ui.TextBox("");
			this.text.setEnabled(false);
			this.resizeTextBox();
			this.appendSubElement(this.text);
			this.text.centerText();		//	center by default
			//this.text.setFrame(1, rat.graphics.white);	//	debug
		}

		return this.text;
	};
	
	//	adjust our textbox's size based on button size and a few adjustment properties.
	rat.ui.Button.prototype.resizeTextBox = function()
	{
		if (this.text)
		{
			this.text.setPos(this.textInset + this.textOffsetX, this.textOffsetY);
			this.text.setSize(this.size.x - 2 * this.textInset, this.size.y);
			
			//	pick a better default font size based on button size.
			//	If the user specified a font size at some point, always use that.
			//	Otherwise, pick what we think is a good font size to fit in our button.
			if (this.useFontSize)
				this.text.setFontSize(this.useFontSize);
			else
				this.text.setFontSize((this.size.y*0.7)|0);
		}
	};
	
	//	todo: maybe never use this?  Is resizeTextBox enough?
	rat.ui.Button.prototype.removeTextBox = function ()
	{
		if (this.text)
		{
			this.removeSubElement(this.text);
			this.text = null;
		}
	};

	//
	//	get reference to our image, if there is one, so it can be directly manipulated.
	//
	rat.ui.Button.prototype.getImage = function ()
	{
		return this.buttonImage;
	};

	rat.ui.Button.prototype.setTextValue = function (value)
	{
		this.checkAndMakeTextBox().setTextValue(value);
		this.name = "<but>" + this.id + "(" + value + ")";
	};

	rat.ui.Button.prototype.translateAndSetTextValue = function (value)
	{
		this.name = "<but>" + this.id + "(" + value + ")";
		this.checkAndMakeTextBox().translateAndSetTextValue(value);
	};
	
	rat.ui.Button.prototype.setFont = function (font, size, style)
	{
		this.checkAndMakeTextBox().setFont(font, size, style);
		if (size)
			this.useFontSize = size;	//	they specified a size here - respect that.
	};

	rat.ui.Button.prototype.setFontStyle = function (style)
	{
		this.checkAndMakeTextBox().setFontStyle(style);
	};

	rat.ui.Button.prototype.setFontSize = function (size)
	{
		this.checkAndMakeTextBox().setFontSize(size);
		this.useFontSize = size;	//	remember in case we rebuild that sucker later
	};
	
	rat.ui.Button.prototype.setCornerRadius = function(rad)
	{
		if (rad != this.cornerRadius)
			this.setDirty(true);
		this.cornerRadius = rad;
		this.cornerRadiusSet = true;
	};
	
	/** 	set text colors for this button
	   	This is the easy comfortable version, where you specify common colors.
	   	For more control, see setStateTextColors
	   	All args but the first are optional
 */
	rat.ui.Button.prototype.setTextColors = function (color, colorHi, colorPressed, colorDisabled)
	{
		//	provide some defaults if not specified
		//	(todo: do this in a lower-level array-based function so everyone benefits? not sure how to best do that.)
		if (typeof colorHi === 'undefined')
			colorHi = color;
		if (typeof colorPressed === 'undefined')
			colorPressed = color;
		if (typeof colorDisabled === 'undefined')
			colorDisabled = color;

		//	build on top of the function setStateTextColors(), which does what we want in a generic way.
		var RE = rat.ui.Element;	//	for readability
		var statePairs = [
			{state: RE.enabledFlag, textColor: color},	//	normal
			{state: RE.enabledFlag | RE.highlightedFlag, textColor: colorHi},	//	highlighted
			{state: RE.enabledFlag | RE.pressedFlag, textColor: colorPressed},	//	pressed
			{state: RE.enabledFlag | RE.toggledFlag, textColor: colorPressed},	//	toggled
			{state: RE.enabledFlag | RE.toggledFlag | RE.highlightedFlag, textColor: colorHi},	//	toggled highlighted
			{state: RE.enabledFlag | RE.toggledFlag | RE.pressedFlag, textColor: color},	//	toggled pressed
			{state: 0, textColor: colorDisabled},	//	disabled
		];
		this.setStateTextColors(statePairs);
	};
	//	alternative name
	rat.ui.Button.prototype.setTextColor = rat.ui.Button.prototype.setTextColors;
	
	/** 	A very flexible way to set up text color states that match various possible button states.
	   	This will add text color info to any matching state, or create a new state if it needs to.
	   	We expect to be passed in an array of state+color pairs, like this:
	   	[
	   		{state: rat.ui.Element.enabledFlag, textColor: rat.graphics.white},
	   		{state: rat.ui.Element.enabledFlag | rat.ui.Element.highlightedFlag, textColor: new rat.graphics.Color(10,200,150)},
	   		{state: rat.ui.Element.enabledFlag | rat.ui.Element.toggledFlag | rat.ui.Element.highlightedFlag | , textColor: rat.graphics.gray},
	   	]
	   
 */
	rat.ui.Button.prototype.setStateTextColors = function (stateColorPairs)
	{
		this.setStatesByField(stateColorPairs, 'textColor');
	};
	rat.ui.Button.prototype.setTextColorStates = rat.ui.Button.prototype.setStateTextColors;	//	old name, backwards compat
	
	/** 	same thing for base color
 */
	rat.ui.Button.prototype.setStateColors = function (stateColorPairs)
	{
		this.setStatesByField(stateColorPairs, 'color');
	};
	
	/** 	same thing for frame color
 */
	rat.ui.Button.prototype.setStateFrameColors = function (stateColorPairs)
	{
		this.setStatesByField(stateColorPairs, 'frameColor');
	};
	
	/** 	and for frame width
 */
	rat.ui.Button.prototype.setStateFrameWidth = function (stateValuePairs)
	{
		this.setStatesByField(stateValuePairs, 'frameWidth');
	};
	
	/** 
	   	A very flexible way to set up images that match various possible button states.
	   	This will work for image buttons, bubble buttons, etc.
	   	This will add image info to any matching state, or create a new state if it needs to.
	   	We expect to be passed in an array of state+resource settings, like this:
	   	[
	   		{state: rat.ui.Element.enabledFlag, resource: "normal.png"},
	   		{state: rat.ui.Element.enabledFlag | rat.ui.Element.highlightedFlag, resource: "high.png"},
	   		{state: rat.ui.Element.enabledFlag | rat.ui.Element.toggledFlag | rat.ui.Element.highlightedFlag, resource: "toggled_and_highlighted.png"},
	   	]
	   	Alternatively, provide in an imageRef directly, instead of a resource, and we'll use that.
	   	Or, set imageRef to null explicitly, if you want us to NOT draw an image in that state.
	   
	   	Use "doUpdate" flag to control whether or not images are automatically updated by this button on each frame.
	   	This is useful, for instance, if you want to use the same image in multiple states, but only want it to be updated once.
	   	By default, doUpdate is set to true for state images, so all images update at the same time, which is nice for keeping state image animations in sync.
	   	If you want to NOT update each image, e.g. if you're reusing the same imageref, then be sure to set doUpdate to false in the data you pass in, e.g.
	   		{ state: rat.ui.Element.enabledFlag, imageRef : myImage, doUpdate : false},
	   	Or, alternatively, use multiple imagerefs, which is kinda what they're for.  They're supposed to be lightweight.
	   	(which might be an argument for removing this "doUpdate" flag stuff entirely anyway)
	   		
 */
	rat.ui.Button.prototype.setStateImages = function (stateImageSets, extra1, extra2)
	{
		//	We're going to set imageRef values below.
		//	But first, for the caller's convenience, let's build imageRef values if they provided a simple resource name instead.
		for (var i = 0; i < stateImageSets.length; i++)
		{
			var set = stateImageSets[i];
			
			//	note:  If imageRef is undefined, go ahead and look for resource.
			//	if imageRef is null, that means they specifically set it that way, and we should respect that, and use null as our value.
			if (typeof(set.imageRef) === "undefined")
			{
				if (!set.resource)
					rat.console.log("Error:  no imageRef or image specified in setStateImages");
				set.imageRef = rat.graphics.makeImage(set.resource, extra1, extra2);
				if (Array.isArray(set.resource))
				{
					//	this is practically guaranteed to be the wrong speed, but at least designer will see it's animating..
					//	The expected way to set the animation speed is something like this:
					//	button.getMatchingState(rat.ui.Element.enabledFlag).imageRef.setAnimSpeed(8);
					//	(after calling setStateImages)
					//	see sample ui code used in rtest
					set.imageRef.setAnimSpeed(1);
				}
			}
			
			//	and if they didn't define "doUpdate" flags, make some up.
			if (typeof(set.doUpdate) === "undefined")
			{
				set.doUpdate = true;
			}
		}
		
		this.setStatesByField(stateImageSets, 'imageRef');
		
		this.setStatesByField(stateImageSets, 'doUpdate');
	};
	
	//
	//	Low-level color state field setting utility, used by all the various "setStateXXXs" functions...
	//	This is an internal function.  In general, you may want to instead call
	//		setStateImages
	//		setStateColors
	//		setStateTextColors
	//		setStateFrameColors
	//		etc...
	//
	//	This is hopefully the main bottleneck for setting state values.
	//	A very flexible way to set up color states that match various possible button states.
	//	This will add info to any existing matching state, or create a new state if it needs to.
	//	We expect to be passed in an array of state+value pairs.  See other functions for examples and details.
	rat.ui.Button.prototype.setStatesByField = function(statePairs, fieldName)
	{
		if (!this.colorStates)	//	no states?  Create the list.
			this.colorStates = [];
	
		//	would it be better to set up some defaults?
		//if (typeof this.colorStates === 'undefined')
		//	this.setDefaultColorStates();
		
		for (var i = 0; i < statePairs.length; i++)
		{
			var statePair = statePairs[i];
			var stateIndex = this.getMatchingStateIndex(statePair.state, true);
			
			var value = statePair[fieldName];
			//	for any color type, support converting from standard style string, for convenience.
			if (fieldName === 'textColor' || fieldName === 'color' || fieldName === 'frameColor')
			{
				if (typeof(value) === 'string')
					value = rat.graphics.Color.makeFromStyleString(value);
			}
			
			this.colorStates[stateIndex][fieldName] = value;
		}
		
		//	let's assume we're setting them because we're using them immediately to set/change the look of the button.
		this.setDirty(true);
	};
	
	/** 	Set this explicit value for all states that currently exist.
	   	So, big assumption that a bunch of useful states exist,
	   	which would be the case if you used one of the built-in shape-based button types.
	   	Of course, you want different states to have some different values, so this is only useful
	   	for things that stay the same, like frame width or font properties or something.
 */
	rat.ui.Button.prototype.setAllStatesByField = function(fieldName, value)
	{
		var newPairs = [];
		for (var i = 0; i < this.colorStates.length; i++)
		{
			var entry = {state: this.colorStates[i].flags};
			entry[fieldName] = value;
			newPairs.push(entry);
		}
		this.setStatesByField(newPairs, fieldName);
	};
	
	/** 	set frame width for all states
 */
	rat.ui.Button.prototype.setFrameWidth = function(value)
	{
		this.setAllStatesByField('frameWidth', value);
	};
	//	todo: same function for other standard props.
	
	//	find exact matching color state in our list,
	//	create new state entry if there wasn't one.
	//	Note that this is VERY different from findBestStateMatch below!
	//	This function matches only exactly, and creates a new entry, so only use it on button creation/setup...
	/**
	 * @param {?} state
	 * @param {boolean=} createNew
	 */
	rat.ui.Button.prototype.getMatchingStateIndex = function(state, createNew)
	{
		//	look for color state with the exact same flags.
		if (!this.colorStates)
			return -1;
		var foundIndex = -1;
		for (var lookIndex = 0; lookIndex < this.colorStates.length; lookIndex++)
		{
			if (this.colorStates[lookIndex].flags === state)
				return lookIndex;
		}
		if (foundIndex < 0)	//	if not found, add to end
		{
			if (!createNew)
				return -1;
				
			foundIndex = this.colorStates.length;
			this.colorStates[foundIndex] = {
				flags: state,
				textColor: rat.graphics.white,	//	need better system of defaults
			};
		}
		return foundIndex;
	};
	
	//	return state structure reference that matches this state,
	//	e.g. for modification of a particular state of a button, e.g. for changing image animation speed
	rat.ui.Button.prototype.getMatchingState = function(state)
	{
		var index = this.getMatchingStateIndex(state);
		if (index >= 0)
			return this.colorStates[index];
		return null;
	};
	
	rat.ui.Button.prototype.setStroke = function (w, c, d)
	{
		this.checkAndMakeTextBox();
		this.text.setStroke(w, c, d);
	};

	//	utility to find the best match for given state list (or our state list, if none were specified)
	//	todo: add priority, if number of matching flags is the same?
	//		or switch to first-is-best?
	//	todo: prioritize 'pressed' match over 'highlight' match, since that's a more important state to show?
	//	return matching state.
	/** 
	 * @param {?} _flags
	 * @param {?} _states
	 */
	rat.ui.Button.prototype.findBestStateMatch = function (_flags, _states)
	{
		if (typeof _flags === 'undefined')
			_flags = this.flags;
		if (!_states)
			_states = this.colorStates;
		if (!_states)
			return null;
			
		var bestIndex = 0;
		var bestFlags = 0;
		for (var i = 0; i < _states.length; i++)
		{
			var matchFlags = 0;
			for (var b = 0; b < 4; b++)
			{
				var checkBit = 1 << b;
				if ((_flags & checkBit) === (_states[i].flags & checkBit))
					matchFlags++;
			}
			if (matchFlags >= bestFlags)	//	if better or equal (latest is best), remember it
			{
				bestIndex = i;
				bestFlags = matchFlags;
			}
		}
		return _states[bestIndex];
	};

	//	called every frame to make sure we're displaying the way we want.
	//	because it's an every frame thing, this is a bad place to worry about dirty flags.
	//	Dirty note:  We assume our dirty flag is set earlier in other cases, like our display-related flags being changed.
	//		If that turns out to be too loose, we'd better do this work before rat.ui.Element.draw decides we're not dirty...
	//		(which happens before drawSelf is called)
	rat.ui.Button.prototype.updateDisplayState = function ()
	{
		var displayState;
		if (typeof this.colorStates !== 'undefined')
		{
			displayState = this.findBestStateMatch(this.flags, this.colorStates);
			if (this.text)
			{
				if (displayState.textColor)
					this.text.setColor(displayState.textColor);
				if (displayState.font)
				{
					if (displayState.font.font)
						this.text.setFont(displayState.font.font);
					if (displayState.font.size)
						this.text.setFontSize(displayState.font.size);
				}
				if (displayState.stroke)
					this.text.setStroke(displayState.stroke.lineWidth || 0, displayState.stroke.color, displayState.stroke.doCleanup);
			}
		}
		return displayState;
	};

	//	Draw this button
	//	The final look depends on current state
	rat.ui.Button.prototype.drawSelf = function ()
	{
		var ctx = rat.graphics.getContext();
		//	based on state, change which image our bubble box uses to draw...
		//	We have many potential state combinations, and may not have all the possible art,
		//	which is to be allowed,
		//	so just try to pick the best possible image...

		//	see if we have display data (colors, images) based on state, regardless of type.
		//	this gets set up for cheap buttons as well as other types if their text colors are explicitly set.
		var displayState = this.updateDisplayState();

		//	cheap button
		if ((this.type === rat.ui.Button.cheapType) && (typeof displayState !== 'undefined'))
		{
			ctx.fillStyle = displayState.color.toString();
			ctx.fillRect(-this.center.x, -this.center.y, this.size.x, this.size.y);

			var lineWidth = 4;
			if (displayState.frameWidth !== void 0)
				lineWidth = displayState.frameWidth;
			ctx.lineWidth = lineWidth;
			ctx.strokeStyle = displayState.frameColor.toString();
			//ctx.strokeRect(-this.center.x + lineWidth / 2, -this.center.y + lineWidth / 2, this.size.x - lineWidth, this.size.y - lineWidth);
			ctx.strokeRect(-this.center.x - lineWidth / 2, -this.center.y - lineWidth / 2, this.size.x + lineWidth, this.size.y + lineWidth);

		//	cool buttton
		} else if ((this.type === rat.ui.Button.coolType) && (typeof displayState !== 'undefined'))
		{
			rat.graphics.roundRect(
				{x : 0-this.center.x, y : 0-this.center.y, w : this.size.x, h : this.size.y},
				this.cornerRadius
			);
			ctx.fillStyle = displayState.color.toString();
			ctx.fill();

			var lineWidth = 4;
			if (displayState.frameWidth !== void 0)
				lineWidth = displayState.frameWidth;
			ctx.lineWidth = lineWidth;
			ctx.lineCap = 'round';	//	to avoid seams
			ctx.strokeStyle = displayState.frameColor.toString();
			ctx.stroke();

		} else if (typeof this.buttonImage !== 'undefined')
		{
			//	based on state, update our buttonImage, which is a direct reference to a subelement with an image in it. (see above).
			//	so, we change it here, and let it draw itself eventually.

			if (displayState && displayState.imageRef)	//	did we find a nice state match above with an image?
			{
				this.buttonImage.useImageRef(displayState.imageRef);

			} else
			{	//	old - use baked-in variables to update image
				var useRef;
				
				if ((this.flags & rat.ui.Element.enabledFlag) === 0)
				{
					useRef = this.imageDisabled;
				} else if ((this.flags & rat.ui.Element.pressedFlag) || (this.flags & rat.ui.Element.toggledFlag))
				{
					//console.log("drawing pressed " + this.name);
					useRef = this.imagePressed;
				} else if (this.flags & rat.ui.Element.highlightedFlag)
				{
					//console.log("drawing high " + this.name);
					useRef = this.imageHi;
				}
				else
				{
					//console.log("drawing normal " + this.name);
					useRef = this.imageNormal;
				}
				if (!useRef)
					useRef = this.imageNormal;
				if (useRef)
					this.buttonImage.useImageRef(useRef);
			}

		}
	};
	
	//	do we need to update button look?
	rat.ui.Button.prototype.updateSelf = function (dt)
	{
		//	update color states, if any
		if (typeof this.colorStates !== 'undefined')
		{
			//var foundIndex = -1;
			for (var lookIndex = 0; lookIndex < this.colorStates.length; lookIndex++)
			{
				//	update image, if any, and if we're supposed to.
				
				//	In order to avoid double-update, don't update any image that's also our current sprite's image, since that gets updated already.
				//	Is this kludgey?  I'm not sure.  Seems OK for now.
				if (this.buttonImage && this.buttonImage.imageRef && this.buttonImage.imageRef === this.colorStates[lookIndex].imageRef)
					continue;
					
				if (this.colorStates[lookIndex].imageRef && this.colorStates[lookIndex].doUpdate)
					this.colorStates[lookIndex].imageRef.update(dt);
			}
		}
	};
	
	//	This function is called for us if any of our state flags (highlight, etc.) changed.
	rat.ui.Button.prototype.flagsChanged = function (oldFlags)
	{
		//	inherited behavior
		rat.ui.Button.prototype.parentPrototype.flagsChanged.call(this);
		
		//	see if we need to update a state based on this.
		//	(stuff we only want to do at the instant the state changes, not during update/draw)
		var displayState = this.findBestStateMatch();
		if (displayState && displayState.imageRef)
		{
			//	reset any one-shot anims.
			if (displayState.imageRef.isAnimOneShot())
				displayState.imageRef.restartAnim();
		}
		
		//	Note that dirty flag setting here is totally handled by the rat.ui.Element.prototype.checkFlagsChanged function.
	};

	//	handle ui-level input event explicitly, so that "enter" ui event triggers this button.
	rat.ui.Button.prototype.handleUIInput = function (event)
	{
		//rat.console.log("button ui");
		var handled = false;
		if (event.which === 'enter')
		{
			var handled = this.trigger();
			if (handled)
				rat.eventMap.fireEvent("uiTriggered", this);

		}
		return handled;
	};

	//	press me (change my state, trigger my callbacks, etc.)
	rat.ui.Button.prototype.trigger = function ()
	{
		if (this.toggles)
		{
			var oldFlags = this.flags;
			if (this.flags & rat.ui.Element.toggledFlag)
				this.flags &= ~rat.ui.Element.toggledFlag;	//	clear
			else
				this.flags |= rat.ui.Element.toggledFlag;	//	set
			this.checkFlagsChanged(oldFlags);
		}
		return rat.ui.Button.prototype.parentPrototype.trigger.call(this); //	inherited trigger
	};
	
	//	handle mouse down event, so we can clearly mark this event handled (eaten up).
	rat.ui.Button.prototype.mouseDown = function (pos, ratEvent)
	{
		//	call inherited to get correct flags cleared/set
		rat.ui.Button.prototype.parentPrototype.mouseDown.call(this);
		
		if (this.flags & rat.ui.Element.enabledFlag)	//	we're a button - we totally handled this click.
			return true;
			
		return false;
	};
	
	//	internal util: automatically size a button to its art
	function autoSizeButton(button)
	{
		button.setSize(button.buttonImage.size.x, button.buttonImage.size.y);
	}
	
	rat.ui.Button.prototype.autoSize = function ()
	{
		if (this.buttonImage.size.x === 0)
			this.buttonImage.setOnLoad(autoSizeButton, this);
		else
			autoSizeButton(this);
	};
	
	rat.ui.Button.prototype.setToggles = function (toggles)
	{
		this.toggles = toggles;
	};
	
	//	reset the default text inset value for all future buttons
	//	(and for all buttons that haven't been changed from the default, and might get their textbox rebuilt)
	rat.ui.Button.setDefaultTextInset = function(value)
	{
		rat.ui.Button.prototype.textInset = value;
	};

	//	editor properties
	rat.ui.Button.editProperties = [
		{ label: "button",
			props: [
				{propName:'buttonType', type:'integer', defValue:rat.ui.Button.coolType, tipText:"1=sprite,2=bubble,3=cheap,4=cool"},	//	todo: dropdown from supported list
				//{propName:'colorStates', type:'color'},	//	hmm... this is complex...
				{propName:'toggles', type:'boolean'},
				
				{propName:'text', type:'string', tipText:"button text (if needed)"},
				{propName:'textColor', type:'color'},
				{propName:'textInset', type:'float', tipText:"how far in from edges text draws"},
				{propName:'textUseOffscreen', type:'boolean'},
				{propName:'cornerRadius', type:'float', tipText:"for button type 4 (cool)"},
			],
		},
		
		//	resources, only relevant for bubble/sprite types
		{ label: "resources", defaultClosed:true,	//	todo: add tooltip here explaining relevance
			props: [
				{propName:'resource', type:'resource'},
				{propName:'resourceHi', type:'resource'},
				{propName:'resPressed', type:'resource'},
				{propName:'resDisabled', type:'resource'},
				{propName:'extra1', type:'resource'},
				{propName:'extra2', type:'resource'},
			],
		},
		
		rat.ui.TextBox.standardFontEditProperties,	//	standard font stuff
	];
		
	/**
	 * Handle setting this up from data
	 */
	rat.ui.Button.setupFromData = function (pane, data, parentBounds)
	{
		rat.ui.data.callParentSetupFromData(rat.ui.Button, pane, data, parentBounds);

		data.extra1 = data.extra1 || void 0;
		data.extra2 = data.extra2 || void 0;
		
		//	todo: support string version of buttontype? like "cheap" and "sprite"?
		//		(do that in a general way that also feeds into editor ui!
		//		maybe general support for element-specific string aliases that map to values of other types.
		//		that should be easy enough.)

		if (data.buttonType === rat.ui.Button.cheapType || data.buttonType === rat.ui.Button.coolType)
		{
			pane.setupButton(data.buttonType, void 0, void 0, void 0, void 0, data.extra1, data.extra2);
			pane.colorStates = data.colors || data.color;
			//	if NONE of those was defined, make something up.
			if (pane.colorStates === void 0)
				pane.colorStates = new rat.graphics.Color(140, 140, 140);
			
			if (Array.isArray(pane.colorStates) === false)
				pane.colorStates = rat.ui.Button.createStandardColorStates(pane.colorStates);

			//	MUST happen after we call setupButton
			if (data.toggles !== void 0)
			{
				pane.setToggles(true);
				pane.setToggled(!!data.toggled);
			}
		}
		else if (data.buttonType === rat.ui.Button.bubbleType ||
				 data.buttonType === rat.ui.Button.spriteType)
		{
			pane.setupButton(
				data.buttonType,
				data.res || data.resource,
				data.resHi || data.resourceHi,
				data.resPressed || data.resourcePressed,
				data.resDisabled || data.resourceDisabled,
				data.extra1,
				data.extra2);
			if (data.toggles)
			{
				//	MUST happen AFTER setupButton
				pane.setToggles(true);
				pane.setToggled(!!data.toggled);

				/** TODO Support setting the toggle images here.
 */
			}
		}
		
		if (data.cornerRadius !== void 0)
			pane.setCornerRadius(data.cornerRadius);
		else
			pane.cornerRadius = (pane.size.y * rat.ui.Button.standardCornerScale)|0

		//	Setup my text
		//	This font handling is currently the same as rat.ui.TextBox
		if (data.font)
		{
			if (data.font.font)
				pane.setFont(data.font.font);
			if (data.font.size)
				pane.setFontSize(data.font.size);
			if (data.font.style)
				pane.setFontStyle(data.font.style);
			if (data.font.stroke)
				pane.setStroke(data.font.stroke.width, data.font.stroke.color);
		}
		
		if (data.textColor)
		{
			pane.checkAndMakeTextBox().setColor(data.textColor);
			
			//	If you're using a cheap button, support overriding colorset text colors with an explicit color.
			if (data.buttonType === rat.ui.Button.cheapType || data.buttonType === rat.ui.Button.coolType)
			{
				for (var i = 0; i < pane.colorStates.length; i++)
				{
					pane.colorStates[i].textColor = new rat.graphics.Color(data.textColor);
				}
			}
		}
		
		if( data.text )
			pane.translateAndSetTextValue(data.text);
		
		if( data.textInset )
			pane.setTextInset(data.textInset);
		
		if (data.textUseOffscreen !== void 0)
			pane.checkAndMakeTextBox().setUseOffscreen(data.textUseOffscreen);
	};
	
});
//--------------------------------------------------------------------------------------------------
//
//	rat.shapes
//
//	A list of basic shape objects
//
rat.modules.add( "rat.utils.r_shapes",
[
	"rat.math.r_math",
	"rat.math.r_vector",
], 
function(rat)
{
	rat.shapes = {};

	/**
	 * Circle
	 * @constructor
	 * @param {number|Object} x center.x or def object
	 * @param {number=} y center.y 
	 * @param {number=} r radius
	 */
	rat.shapes.Circle = function (x, y, r)
	{
		//	x may also be an object which defines this circle
		var squaredRadius = 0;
		if(x.x !== void 0)
		{
			y = x.y;
			if (x.squaredRadius !== void 0)
				squaredRadius = x.squaredRadius;
			else
				squaredRadius = (x.r * x.r) || 0;
			if( x.r !== void 0 )
				r = x.r;
			else
				r = rat.math.sqrt(squaredRadius);
			x = x.x;
		}
		this.center = { x:x||0, y:y||0 };
		this.radius = r || 0;
		this.squaredRadius = squaredRadius;
	};

	/**
	 * Rect
	 * @constructor
	 * @param {number|Object=} x
	 * @param {number=} y
	 * @param {number=} w
	 * @param {number=} h
	 */
	rat.shapes.Rect = function (x, y, w, h)	//	constructor for Rect
	{
		if(x === void 0) {
			this.x = 0;
			this.y = 0;
			this.w = 0;
			this.h = 0;
		} else if(x.x !== void 0) {
			this.x = x.x;
			this.y = x.y;
			this.w = x.w;
			this.h = x.h;
		} else {
			this.x = x;
			this.y = y;
			this.w = w;
			this.h = h;
		}
	};

	rat.shapes.Rect.prototype.copy = function ()
	{
		var newRect = new rat.shapes.Rect();
		newRect.x = this.x;
		newRect.y = this.y;
		newRect.w = this.w;
		newRect.h = this.h;
		return newRect;
	};
	
	//	expand this rect (position and w,h) to include all of another rect
	rat.shapes.Rect.prototype.expandToInclude = function (r)
	{
		if(r.x < this.x)
			this.x = r.x;
		if(r.y < this.y)
			this.y = r.y;
		if(r.x + r.w > this.x + this.w)
			this.w = r.x + r.w - this.x;
		if(r.y + r.h > this.y + this.h)
			this.h = r.y + r.h - this.y;
	};

	//	Get the center
	rat.shapes.Rect.prototype.getCenter = function (dest)
	{
		dest = dest || new rat.Vector();
		dest.x = this.x + (this.w / 2);
		dest.y = this.y + (this.h / 2);
		return dest;
	};

	//	Calc (and add to this object) my right and bottom values
	rat.shapes.Rect.prototype.calcEdges = function ()
	{
		this.l = this.x;
		this.t = this.y;
		this.r = this.x + this.w;
		this.b = this.y + this.h;
		return this;
	};
	
	
	
	//	Return if the provided point is in this rect (edges included)
	//	Requires CalcEdges to be called first
	rat.shapes.Rect.prototype.isPointInRect = function( x, y )
	{
		if( x.x != void 0 )
		{
			y = x.y;
			x = x.x;
		}
		return x >= this.l && x <= this.r &&
				 y >= this.t && y <= this.b;
	};
} );
//--------------------------------------------------------------------------------------------------
/*
	some limited voice command support - top level system-independent version
*/
rat.modules.add( "rat.input.r_voice",
[
	{name: "rat.os.r_system", processBefore: true},
	
	{name: "rat.input.r_voice_xbo", platform: "xbox"} // platform specific versions run AFTER me
], 
function(rat)
{
	// move this to a platform independent file if/when we generalize this
	rat.voice = {
		commands: {
			Play: "play",				//	The play button. 
			Pause: "pause",				//	The pause button. 
			Stop: "stop",				//	The stop button. 
			Record: "record",			//	The record button. 
			FastForward: "fastForward",	//	The fast forward button. 
			Rewind: "rewind",			//	The rewind button. 
			Next: "next",				//	The next button. 
			Previous: "previous",		//	The previous button. 
			ChannelUp: "channelUp",		//	The channel up button. 
			ChannelDown: "channelDown",	//  The channel down button. 
			Back: "back",				//	The back button. 
			View:  "view",				//	The view button. 
			Menu: "menu",				//	The menu button. 
		},
		
		callbacks: {},	//	Array for each command

		enabled: {}
	};
	
	var firingCB = void 0;
	
	//	Register a callback from a command
	rat.voice.registerCB = function( command, func, ctx )
	{
		var callbacks = rat.voice.callbacks[command];
		if( !callbacks )
			return;
		callbacks.push( {func: func, ctx: ctx} );
	};
	
	//	Unregister a callback from the command
	rat.voice.unregisterCB = function( command, func, ctx )
	{
		var callbacks = rat.voice.callbacks[command];
		if( !callbacks )
			return;
		var cb;
		for( var index = 0; index !== callbacks.length; ++index )
		{
			cb = callbacks[index];
			if( cb.func === func && cb.ctx === ctx )
			{
				if( firingCB && firingCB.index >= index )
					--firingCB.index;
				callbacks.splice( index, 1 );
				return;
			}
		}
	};
	
	//	Fire callbacks for a command
	rat.voice.fireCB = function( command, sys )
	{
		var callbacks = rat.voice.callbacks[command];
		if( !callbacks )
			return;
		var saved = firingCB;
		firingCB = {
			index: 0
		};
		var handled = false;
		var func, ctx;
		for( firingCB.index = 0; firingCB.index !== callbacks.length; ++firingCB.index )
		{
			func = callbacks[firingCB.index].func;
			ctx = callbacks[firingCB.index].ctx;
			handled = func.call(ctx, command);
			if (handled)
				break;
		}
		
		firingCB = saved;
		if (!handled && rat.input)
		{
			var ratEvent = new rat.input.Event(sys, { type: 'voice', defaultControllerID: 'voice', which:command });
			rat.input.dispatchEvent(ratEvent);
		}
		return handled;
	};
	
	rat.voice.enableCommand = function (command, isEnabled)
	{
		if( isEnabled === void 0 )
			isEnabled = true;
		else
			isEnabled = !!isEnabled;
		rat.voice.enabled[command] = isEnabled;
		if( rat.voice._internalEnabledCommand )
			rat.voice._internalEnabledCommand( command, isEnabled );
	};

	//	Disable all callbacks
	rat.voice.resetCommands = function()
	{
		//	Add the array to callbacks for each command
		for( var cmd in rat.voice.commands )
		{
			rat.voice.callbacks[rat.voice.commands[cmd]] = [];
			rat.voice.enableCommand(rat.voice.commands[cmd], false);
		}
	};

} );
//--------------------------------------------------------------------------------------------------
//
//	Keyboard management
//
//	Track what keys are down, and which keys are newly down.
//	Do this with bitfields, because I don't like the idea of so much wasted space.  Don't judge me.
//
rat.modules.add( "rat.input.r_keyboard",
[
	{name: "rat.input.r_input", processBefore: true },
], 
function(rat)
{
	rat.input.keyboard = {

		MAX_KEY_CODE: 256,
		KEY_SLOTS: 8,	//	256 keyss, at 32 bits per slot = 256/32
		//	these are collections of bitfields, for optimal use of space
		rawKeys: [0, 0, 0, 0, 0, 0, 0, 0],
		newKeys: [0, 0, 0, 0, 0, 0, 0, 0],
		lastKeys: [0, 0, 0, 0, 0, 0, 0, 0],

		update : function(dt)
		{
			var kb = rat.input.keyboard;
			for( var i = 0; i < kb.KEY_SLOTS; i++ )
			{
				kb.newKeys[i] = kb.rawKeys[i] & ~kb.lastKeys[i];	//	flag which keys were newly down this frame
				kb.lastKeys[i] = kb.rawKeys[i];
			}
		},

		handleKeyDown: function(e)	//	handle raw system event
		{
			var which = rat.input.getEventWhich(e);
			var slot = rat.math.floor(which / 32);
			var bit = which - slot * 32;

			rat.input.keyboard.rawKeys[slot] |= (1 << bit);
		},

		handleKeyUp: function(e)
		{
			var which = rat.input.getEventWhich(e);
			var slot = rat.math.floor(which / 32);
			var bit = which - slot * 32;

			rat.input.keyboard.rawKeys[slot] &= ~(1 << bit);
		},

		isKeyDown: function(keyCode)
		{
			var slot = rat.math.floor(keyCode / 32);
			var bit = keyCode - slot * 32;
			if (rat.input.keyboard.rawKeys[slot] & (1 << bit))
				return true;
			else
				return false;
		},

		//	think about doing ui event handling instead, which is more reliable and filtered by active screen
		isKeyNewlyDown: function(keyCode)
		{
			var slot = rat.math.floor(keyCode / 32);
			var bit = keyCode - slot * 32;
			if (rat.input.keyboard.newKeys[slot] & (1 << bit))
				return true;
			else
				return false;
		},

	};
});
//--------------------------------------------------------------------------------------------------
//
//	A system for broadcasting messages to registered listeners
//
rat.modules.add( "rat.utils.r_messenger",
[], 
function(rat)
{
	/**
	 * @constructor
	 */
	rat.Messenger = function ()
	{
		this.handlers = [];
	};

	// Returned by listeners to tell the system to remove them.
	rat.Messenger.remove = "REMOVE"; 

	rat.Messenger.prototype.broadcast = function (name)
	{
		var handlers = this.handlers[name];
		if(handlers)
		{
			var funcsToRemove = [];
			var index;

			Array.prototype.splice.call(arguments, 0, 1);
			for(index in handlers)
			{
				if(handlers.hasOwnProperty(index))
				{
					//We do not work with contexts in this system,
					// using null in apply will provide the global object
					// in place of 'this'
					//handlers[index].apply(null, arguments);
					// However, this does mean that if i have use a bind to provide the handler, then it gets lost.
					// Instead, store the func in  var, and call it
					var func = handlers[index];
					//func();
					var res = func.apply(this, arguments);
					if (res === rat.Messenger.remove)
						funcsToRemove.push(func);
				}
			}
			for (index = 0; index !== funcsToRemove.length; ++index)
				this.stopListening(name, funcsToRemove[index]);
		}
	};

	rat.Messenger.prototype.listen = function (name, callback) {
		if (typeof callback === 'function') {
			this.handlers[name] = this.handlers[name] || [];
			var index = this.handlers[name].indexOf(callback);
			if (index === -1) {
				this.handlers[name].push(callback);
			}
		}
	};

	rat.Messenger.prototype.stopListening = function (name, callback) {
		if (typeof callback === 'function') {
			var index = this.handlers[name].indexOf(callback);
			if(index !== -1)
				this.handlers[name].splice(index, 1);
		}
	};

} );
//--------------------------------------------------------------------------------------------------
//
// Rat Image and ImageRef module
//
// Image loading, caching, sharing, animating, and other stuff
//
// Image:  an object to store all data needed for an image, including tracking multiple frames
//  this is for the image data, not information about rendering it (e.g. location, etc.)
//
// ImageRef:  a reference to an image, with runtime rendering info like which frame we're on in an animation.
//  this is separate so that each use can have its own current frame, but still collect
//  all the logic in one place instead of in each chunk of code using images, and make it easy
//  to intermix animated and nonanimated images
//
// Image Cache:  a cache of images...
//  Some browsers might cache images, but in practice I've found that they generally don't.  (e.g. chrome 24)
//  amd even then we'd still have to wait for it to call our onload function to get details like size.
//  so, for our convenience in being able to preload stuff and later use it immediately,
//  we preload into a cache (preLoadImages) and keep those images around.
//  maybe could have worked around the details, but this means we don't have to worry about
//  some implementations not caching images, or dumping them on a whim, or whatever.
//
// Hmm...  We do need some kind of way to queue up a bunch of loads, show a loading screen,
// and then move on.  Should that support be here?
// this per-image onLoad stuff is annoying...
//
// TODO:  See notes in docs/ for things to do with this system, ideas for rewriting it, etc.
//------------------------------------------------------------------------------------
rat.modules.add("rat.graphics.r_image",
[
	{ name: "rat.graphics.r_graphics", processBefore: true },

	{ name: "rat.debug.r_console", processBefore: true},
	"rat.os.r_system",
],
function (rat)
{
	/** 
	    Image Object
	    @constructor
	   
 */
	rat.graphics.Image = function ()
	{
		this.imageFrames = []; // separate list of Image() objects
		this.frames = null; // this is used for spritesheet-based images - a list of spritesheet frame info
		this.frameEvents = void 0;
		this.loadedCount = 0;
		this.loadedTargetCount = 0;
	};
	rat.graphics.Image.prototype.isImage = true; // for ease of detection
	
	rat.graphics.Image.centeredX = 0x0001;
	rat.graphics.Image.centeredY = 0x0002;
	rat.graphics.Image.flipX = 0x0004;
	rat.graphics.Image.flipY = 0x0008;

	// some system-wide properties (see below)
	rat.graphics.Image.lastRenderFrameIndex = -1;
	rat.graphics.Image.perFrameDrawCount = 0;

	// todo:  rename these "varName" or something, for clarity below.  These are all JSON file variable names,
	// Very indirect and kinda confusing.  I don't think they change, but by referring to them with variable names, we avoid problems with compilers renaming stuff
	//  which would then not match literal names in JSON files.
	// For some variable names (e.g. 'x', 'y', 'w', 'h') I use the literal names below, instead of using an indirect variable here.  Maybe do that everywhere?
	var frameName = 'frame'; // this is the name of the variable in a spritesheet file that holds frame info.
	var framesName = 'frames'; // this is the name of the variable in a spritesheet file that holds the list of frames
	var fileNameName = 'filename';
	var srcSizeName = 'spriteSourceSize'; // this is the size of the section of image we want to render?  different from "frame" if trimmed...
	var origSizeName = 'sourceSize'; // this is the *original* size of the original-pre-packed image.  Important to know to make spritesheet usage transparent.

	// note:  don't use flip, if you can avoid it, for performance reasons. (though, I don't think it's a super huge deal)
	//  see http://stackoverflow.com/questions/7918803/flip-a-sprite-in-canvas and http://jsperf.com/ctx-flip-performance/5

	/**  get the image (raw html5 image) for this frame, if it exists and is ready to draw.
	    otherwise return null
	   	Note: don't use this if you want spritesheets to work.  This returns the raw image.
	    @method
	    @param {number} frameNum 
	    @param {Object=} options 
	   
 */
	rat.graphics.Image.prototype.getImageFrame = function (frameNum, options)
	{
		if (!this.imageFrames)
		{
			return null;
		}
		if (frameNum < 0 || frameNum >= this.imageFrames.length)
		{
			if (!options || !options.noWarn)
			{
				if (this.imageFrames.length <= 0)
					rat.console.log("Bad image frame! Failed to specify/load any images?");
				else
					rat.console.log("Bad image frame! " + frameNum + " / " + this.imageFrames.length);
			}
			return null;
		}

		if (!this.imageFrames[frameNum].valid)
		{
			return null;
		}

		return this.imageFrames[frameNum];
	};

	rat.graphics.Image.prototype.isLoaded = function ()
	{
		for (var i = 0; i < this.imageFrames.length; i++)
		{
			//	check valid or error to know if load attempt is done.
			if (!this.imageFrames[i].valid && !this.imageFrames[i].error)
				return false;
		}
		return true;
	};

	//
	//	Add these frames to this image's frame list
	//	This is really the main chokepoint for loading, for images, single-frame or multi-frame.
	//	(start things loading)
	//	TODO: clean up canvas support below.
	//	TODO: support Image() object type.
	//
	rat.graphics.Image.prototype.appendFrames = function (inList)
	{
		if (!inList)
			return;
		
		//	handle list of sources or single source
		var sourceList = inList;
		if (!Array.isArray(inList))
		{
			sourceList = [inList];
		}
		//	what type of thing are we being given, here?  Could be resource name or canvas...
		//	TODO: Why do we only support one type in a list?
		var sourceType = typeof (sourceList[0]);
		
		//	set debug name for convenience
		if (sourceType === 'string')
			this.debugName = sourceList[0];

		//	can we find this in already loaded sprite sheets?
		if (sourceType === 'string' && this.findAndBuildFromSpriteSheets(sourceList))
			return;

		function onImgLoadComplete(img)
		{
			//console.log("loaded " + img.src + "\n");
			img.onload = null;	//	don't let anyone call me again.  See logic below.  Depends on browser.
			img.valid = true;
			this.loadedCount++;
			if (this.loadedCount >= this.loadedTargetCount && this.onLoad)
			{
				//console.log("finished loading " + this.loadedCount + " images");
				//console.log("first is " + this.frames[0].valid);
				//console.log("length is " + this.frames.length);

				this.onLoad(img); //	done loading all frames
			}
		}

		function onImgLoadError(img)
		{
			img.onerror = null;
			rat.console.log("ERROR loading image " + img.src);
			this.loadedCount++;
			img.error = true;	//	so we can skip it or track back problems later
		}

		//	load normally through cached images
		this.loadedTargetCount = sourceList.length;
		for (var i = 0; i < sourceList.length; i++)
		{
			//console.log("loading... " + sourceList[i] + "\n");

			var image = null;
			if (sourceType === 'string')
				image = rat.graphics.findInCache(sourceList[i]);
			if (image !== null)
			{
				//	already in cache - just use that.
				//console.log("   found in cache " + image.origSrc + "\n");
				this.loadedCount++;
			} else
			{
				if (sourceType === 'string')
				{
					//console.log( "   making new image... "+sourceList[i]+"\n" );
					image = new Image();
					image.valid = false;	//	not yet loaded
					image.error = false;

					image.onload = onImgLoadComplete.bind(this, image);
					image.origSrc = rat.utils.cleanPath(sourceList[i]);	//	remember original relative source
					image.onerror = onImgLoadError.bind(this, image);
					
					//	a good place to debug actual image load attempts!
					//	This should be the only place in this module that it happens.
					//rat.console.log("Loading image " + image.origSrc + "...");

					image.src = rat.system.fixPath(sourceList[i]);	//	will eventually get changed to full source path

					//console.log( "   Set src " + image.src + "\n" );

					//	a quick test.  does this work?
					//	If this image is already loaded, it might be nice to use it now instead of wait until onload gets called,
					//	because sometimes onload is not immediately called, even if the image is loaded.
					//	confirm this by checking width.
					if (image.width > 0 && image.onload)
						onImgLoadComplete.call(this, image); // this will clear the onload method

					//	todo: consider adding this to cache automatically
					//	currently, this function is already being used by cache to create new entries, so be careful with that.

				} else
				{
					//	support canvases.  Let's start by assuming that's what this is.
					//	TODO: step back a level - store an object that contains this canvas, or image,
					//		and has custom fields at THAT level instead of adding fields to a system canvas/image object,
					//		as we do here and above.  :(  Pretty bogus.

					//	UGH, this is really not ideal.  If the canvas we're loading comes from an image we have to wait for,
					//	then all kinds of things are wrong here... :(
					//	Right now we assume the canvas exists and is all set up.
					console.log("Trying to load non-string-resource image...");
					image = sourceList[i];	//	canvas
					image.valid = true;
					//	origSrc = leave it empty to force a different kind of check in findInCache, which .. is weird!
					this.loadedCount++;
				}
			}

			this.imageFrames.push(image);
		}
	};
	
	rat.graphics.Image.spriteSheets = [];
	rat.graphics.Image.spriteSheets.numInMemory = 0;

	function SpriteSheet(imagePath, tpjs, opts)
	{
		this.image = imagePath;
		this.tpjs = tpjs;
		this.opts = opts;
		if (tpjs['meta'] && tpjs['meta'])
			this.scaledBy = Number(tpjs['meta']['scale']) || 1;
		else
			this.scaledBy = 1;
		this.undoScaler = 1/this.scaledBy;
		++rat.graphics.Image.spriteSheets.numInMemory;
	}
	SpriteSheet.prototype.inMemory = true;
	
	//	Load/unload a spritesheet from memory
	SpriteSheet.prototype.setActive = function( active )
	{
		if( active === void 0 )
			active = true;
		if( active == this.inMemory )
			return;
		
		if( !active )
		{
			this.inMemory = false;
			rat.graphics.clearFromCache( this.image );
			--rat.graphics.Image.spriteSheets.numInMemory;
		}
		else
		{
			rat.graphics.preLoadImages([this.image]);
			this.inMemory = true;
			++rat.graphics.Image.spriteSheets.numInMemory;
		}
		rat.console.log( (active ? "" : "UN-") + "loading spritesheet " + this.image );
	};
	
	//	load and register spritesheet from texturepacker js file
	//	this optionally caches the image (if it's not already cached)
	//	and records all the frame info, which can be used later on a frame by frame basis
	rat.graphics.Image.registerSpriteSheet = function (imagePath, tpjs, opts)
	{
		if (rat.graphics.Image.getSpriteSheetIndex(tpjs) >= 0)	//	already here
			return;
		if( !opts )
			opts = {};
		
		//	default to preloading image.
		//	Note that there's not much downside to trying to preload something that's already
		//	preloaded, but if you want to avoid this preload (e.g. if you're going to register
		//	the spritesheet but use it later and don't want to use up space with the image)
		//	then you can specify opts.preloadImage = false.
		if (opts.preloadImage === void 0)
			opts.preloadImage = true;
		if (opts.preloadImage && imagePath)
			rat.graphics.preLoadImages( imagePath );
		
		var sheet = new SpriteSheet( imagePath, tpjs, opts );

		//	we support two kinds of tpjs files:  array of frames, and object with each frame named.
		//	Since most of the code in this module is designed to work with an array of frames, adapt the other type now.
		//	Note that it would be a lot more optimal to use the other approach (named variables instead of array) when searching for images,
		//	but for now I'm not going to worry about it - it's usually a one-time thing.
		//	If needed, we could later support them both more aggressively throughout this file, instead of translating here.

		var frameList = tpjs[framesName];
		if (!Array.isArray(frameList))
		{
			tpjs.namedFrames = frameList;	//	store for future reference in original form
			tpjs[framesName] = [];
			//	and build an array instead...
			for (var prop in frameList)	//	go through all properties in frame list (e.g. frame names.  "prop" here is just the property name)
			{
				if (frameList.hasOwnProperty(prop))
				{
					if (opts.forceLowerCase)
						frameList[prop][fileNameName] = prop.toLowerCase();
					else
						frameList[prop][fileNameName] = prop;
					
					tpjs[framesName].push(frameList[prop]);
				}
			}
		}
		
		if (opts.forceLowerCase)
		{
			var texPath = tpjs['meta']['image'];
			if (texPath)
				tpjs['meta']['image'] = texPath.toLowerCase();
		}
		
		if (opts.verbose)
		{
			rat.console.log("loaded spritesheet " + imagePath + " with " + sheet.tpjs[framesName].length + " frames");
		}
		rat.graphics.Image.spriteSheets.push(sheet);
	};
	
	//	Get how many sprite sheets exist
	//	This can be setup to only include sheets that are in memory
	rat.graphics.Image.getSpriteSheetCount = function(opts)
	{
		if( opts && opts.inMemory )
			return rat.graphics.Image.spriteSheets.numInMemory
		else
			return rat.graphics.Image.spriteSheets.length;
	};
	
	//	Get the nth spritesheet
	//	This can be setup to only include sheets that are in memory
	rat.graphics.Image.getNthSpriteSheet = function(nth, opts)
	{
		var sheets = rat.graphics.Image.spriteSheets;
		if( opts && opts.inMemory )
		{
			for( var index = 0; index < sheets.length; ++index)
			{
				var sheet = rat.graphics.Image.spriteSheets[index];
				if( sheet.inMemory )
				{
					--nth;
					if( nth < 0 )
						return sheet;
				}
			}
			return void 0;
		}
		else
			return sheets[nth];
	};
	
	//	Operate on each spritesheet that we have
	//	Can be setup to only run on sheets that are in memory
	rat.graphics.Image.forEachSpriteSheet = function( func, opts )
	{
		if( !func )
			return;
		var nth = 0;
		var sheet;
		while( (sheet = rat.graphics.Image.getNthSpriteSheet( nth, opts )) )
		{
			++nth;
			func( sheet, opts );
		}
	};

	//	find sprite sheet index by reference to tpjs
	rat.graphics.Image.getSpriteSheetIndex = function (tpjs)
	{
		for (var sheetIndex = 0; sheetIndex < rat.graphics.Image.spriteSheets.length; sheetIndex++)
		{
			if (rat.graphics.Image.spriteSheets[sheetIndex].tpjs === tpjs)
				return sheetIndex;
		}
		return -1;
	};

	//	Find this list of resources in one of our loaded sprite sheets.
	//	If found, return sprite sheet index and an array of indices into the frame list
	//	Note:  We access texture sheet (tpjs) vars (e.g. "spriteSourceSize") by literal name,
	//	since they're defined that way in the js files...  more convenient this way,
	//	rather than rewriting those files...  (though, it makes the code hard to read.  would be nice to clean up somehow)
	//	NOTE:  We only support finding them all in one sheet, not across sheets, currently.
	rat.graphics.Image.findInSpriteSheet = function (origResList)
	{
		//	simple case - we never loaded spritesheets.
		if (!rat.graphics.Image.spriteSheets || rat.graphics.Image.spriteSheets.length < 1)
			return null;
		
		//	We're going to search twice.  Once with full name matching including path, and then again without path (if needed).
		//	Why?
		//	We do the first path with full path matching so we can have several spritesheets with similar names,
		//	e.g. if two characters each have a "death_1.png" file, we need to be able to distinguish between them.
		//	We do the second search without full path matching as a fallback, to make it easier to set up spriteSheets
		//	with a bunch of files just thrown into them without any concern about where they came from.
		
		//	so, here's a search function we can use...
		function searchSheets(resList, doMatchPath)
		{
			for (var sheetIndex = 0; sheetIndex !== rat.graphics.Image.spriteSheets.length; ++sheetIndex)
			{
				var sheetEntry = rat.graphics.Image.spriteSheets[sheetIndex];
				var tpjs = sheetEntry.tpjs;
				var frames = tpjs[framesName];	//	list of frames in spritesheet to search
				
				var frameIndexList;	//	list of collected frames that match
				frameIndexList = [];
				
				var prefix = "";
				if (doMatchPath)
				{
					//	todo: when registering spritesheets, support manually setting an alternative search prefix path.
					var texPath = tpjs['meta'];
					if (!texPath)
						continue;
					var texPath = texPath['image'];
					if (!texPath)
						continue;	//	no image path, so we can't use it as a prefix, so don't bother
					
					prefix = rat.utils.getPath(texPath, true);
					
				} else {	//	match without path?
					//	if this sheet requires path matching, skip it.
					if (sheetEntry.opts.requirePath)
						continue;
				}
				
				for (resIndex = 0; resIndex !== resList.length; ++resIndex)
				{
					var lowerRes = null;	//	only created if needed below
					
					for (var frameIndex = 0; frameIndex !== frames.length; ++frameIndex)
					{
						//	Note: sprite sheets sometimes store a simple file name, and sometimes a partial path,
						//	e.g. if texturePacker is using "smartFolders".
						
						var res;
						if (sheetEntry.opts.forceLowerCase)
						{
							if (!lowerRes)	//	first time we needed this?
								lowerRes = resList[resIndex].toLowerCase();
							res = lowerRes;
						} else
							res = resList[resIndex];

						if (prefix + frames[frameIndex][fileNameName] === res)
						{
							frameIndexList.push(frameIndex);
							break;	//	got it, don't keep searching for this one
						}
					}
				}
				
				//	did we find them all?  NOTE:  We seem to only support finding them all in one sheet, not across sheets, currently.
				if (frameIndexList.length === resList.length)
					return { sheetIndex: sheetIndex, frameIndexList: frameIndexList };
				else
					frameIndexList = [];
			}
			return null;
		}
		
		//	search first with paths
		//	Build list of fixed names in case they have .. in them.
		var newResList = [];
		for (var resIndex = 0; resIndex !== origResList.length; ++resIndex)
		{
			newResList[resIndex] = rat.utils.cleanPath(origResList[resIndex]);
			
			//	temp debug stuff
			//if (newResList[resIndex].indexOf("out/media/img/char/lvl1_m/attack_1.png") >= 0)
			//rat.console.log("will be searching for " + newResList[resIndex]);
		}
		var res = searchSheets(newResList, true);
		if (res)
			return res;
		
		//	temp debug
		//if (newResList[0].indexOf("Lvl1_m/attack_1.png") >= 0)
		//	rat.console.logOnce("!couldn't find " + newResList[0], 'nofind');
		
		//	then without...
		//	Build a version of resList that does not include path.
		var shortResList = [];
		for (var resIndex = 0; resIndex !== origResList.length; ++resIndex)
		{
			shortResList[resIndex] = rat.utils.stripPath(origResList[resIndex]);
		}
		
		return searchSheets(shortResList, false);
	};

	//	Attempt to build my frames from one of the registered spritesheets
	//	the list passed in is a list of resource names
	rat.graphics.Image.prototype.findAndBuildFromSpriteSheets = function (resList)
	{
		var foundInfo = rat.graphics.Image.findInSpriteSheet(resList);
		if (!foundInfo)
			return false;

		this.buildFramesFromSpriteSheets(foundInfo.sheetIndex, foundInfo.frameIndexList);
		return true;
	};

	//
	//	Build my frames from this specified sheet in our list, and this frame index list.
	//
	rat.graphics.Image.prototype.buildFramesFromSpriteSheets = function (sheetIndex, frameIndexList)
	{
		var sheetEntry = rat.graphics.Image.spriteSheets[sheetIndex];
		var tpjs = sheetEntry.tpjs;

		this.appendFrames(sheetEntry.image);	//	set our one image to be the one for this sheet.  will use cached image if possible.

		//	copy a bunch of data over.  See note above about tpjs literal field name access
		this.frames = [];	//	multi-frame from internal list
		for (var fIndex = 0; fIndex < frameIndexList.length; fIndex++)
		{
			var frame = {};

			var tpFrameIndex = frameIndexList[fIndex];
			var tpFrame = tpjs[framesName][tpFrameIndex];

			var name = tpFrame[fileNameName];
			frame.name = name.substring(0, name.length - 4);
			frame.scaledBy = sheetEntry.scaledBy;
			frame.undoScaler = sheetEntry.undoScaler;

			frame.drawBox = {
				x: tpFrame[frameName]['x'],
				y:	tpFrame[frameName]['y'],
				w:	tpFrame[frameName]['w'],
				h:	tpFrame[frameName]['h']
			};
			
			frame.box = {};
			frame.box.x = frame.drawBox.x * sheetEntry.undoScaler;
			frame.box.y = frame.drawBox.y * sheetEntry.undoScaler;
			frame.box.w = frame.drawBox.w * sheetEntry.undoScaler;
			frame.box.h = frame.drawBox.h * sheetEntry.undoScaler;

			var trimFrame = tpFrame[srcSizeName];
			frame.trimRect = {
				x: trimFrame['x'] * sheetEntry.undoScaler,
				y: trimFrame['y'] * sheetEntry.undoScaler,
				w: trimFrame['w'] * sheetEntry.undoScaler,
				h: trimFrame['h'] * sheetEntry.undoScaler };

			var sourceSize = tpFrame[srcSizeName];
			frame.sourceSize = { w: sourceSize['w'], h: sourceSize['h'] };

			//	original size of original image before being packed.  This is the size we'll report if anyone asks how big we are.
			var origSize = tpFrame[origSizeName];
			var oW, oH;
			if( origSize['w'] )
				oW = origSize['w'] * sheetEntry.undoScaler;
			else
				oW = frame.box.w;
			if( origSize['h'] )
				oH = origSize['h'] * sheetEntry.undoScaler;
			else
				oH = frame.box.h;
			frame.origSize = {
				w: oW,
				h: oH
			};

			//var name = tpFrame['filename'];
			//frame.name = name.substring(0, name.length-4);

			this.frames.push(frame);
		}
	};

	//
	//	build this image automatically from all the frames in a tpjs (by tpjs reference passed in)
	//	(so, this is useful if the entire spritesheet is a single animation, in order)
	//
	rat.graphics.Image.prototype.buildFromSpriteSheet = function (tpjs)
	{
		var sheetIndex = rat.graphics.Image.getSpriteSheetIndex(tpjs);
		if (sheetIndex < 0)
			return;

		//	just make a list of indices and use our function above.
		var frameIndexList = [];
		for (var i = 0; i < tpjs[framesName].length; i++) // I assume that this is ['frames'] because the JSON is not minimized?
		{
			frameIndexList[i] = i;
		}

		this.buildFramesFromSpriteSheets(sheetIndex, frameIndexList);
	};
	
	//
	//	Load and register a set of spritesheets from this array of sheet data
	//	(which is already loaded - e.g. loaded as a js file, or previously loaded from json)
	//	This includes caching the spritesheet's image.
	//
	rat.graphics.Image.loadSpriteSheetSet = function(sheets, sheetPath, verbose)
	{
		if (!sheets)
			return;
		
		if (verbose)
			rat.console.log("loading " + sheets.length + " spritesheets.");

		for( var sheetIndex = 0; sheetIndex < sheets.length; ++sheetIndex )
		{
			var sheet = sheets[sheetIndex];
			
			var imagePath;
			if (sheetPath)
			{
				//	fix image path to correct directory
				imagePath = sheetPath + rat.utils.stripPath( sheet.meta.image );
			} else
				imagePath = sheet.meta.image;

			rat.graphics.preLoadImages( imagePath );
			
			var ops = {
				forceLowerCase:true,
				requirePath: rat.utils.getPath(sheet.meta.image) !== "",
				verbose: verbose,
				preloadImage : false,	//	we did it manually just now.  :)
			};
			rat.graphics.Image.registerSpriteSheet( imagePath, sheet, ops );
		}
	};
	
	/*
		sample code for an alternative version of the above that uses a preloader object.
		Steve is not sure why, since it eventually just calls rat.graphics.preLoadImages anyway...
		//	Create the preloader.
		var images=[];
		for( var sheetIndex = 0; sheetIndex < sheets.length; ++sheetIndex )
		{
		  var sheet = sheets[sheetIndex];

		  var sheet = sheets[sheetIndex];
		  var filename = rat.utils.stripPath( sheet.meta.image );
		  filename = "<SHEET_IMG_DIR>" + filename;
		  var ops = {
		   forceLowerCase:true,
		   requirePath: rat.utils.getPath(sheet.meta.image) !== "",
		   verbose: <true/false>
		  };
		  rat.graphics.Image.registerSpriteSheet( imagePath, sheet, ops )
		  images.push( filename
		}

		var preloader = new rat.utils.Preloader(
		{
		  "images": images
		});

		preloader.startLoad( function()
		{
		  rat.console.log( "Atlas images loaded!" );
		},
	*/
	

	//	return frame count for this image
	rat.graphics.Image.prototype.frameCount = function ()
	{
		//	multi-part?  if so, return that count
		if (this.frames)
			return this.frames.length;

		//	otherwise return raw image count, since those are our frames
		return this.imageFrames.length;
	};

	//	return the index of the frame with this name, if any
	//	otherwise return -1
	rat.graphics.Image.prototype.getNamedFrame = function (name)
	{
		//	multi-part?  if so, return that count
		if (this.frames)
		{
			for (var i = 0; i < this.frames.length; i++)
			{
				if (this.frames[i].name === name)
					return i;
			}
		}

		//	TODO:  look through our images and see if one has the right name.
		//this.imageFrames.length;
		return -1;
	};

	//	return frame size
	//	returned object is in the form {w:..., h:...}
	rat.graphics.Image.prototype.getFrameSize = function (frameIndex)
	{
		if (this.frames)	//	sprite sheet based, with a list of frames?  Return the size from that frame info.
		{
			//	STT 2015.1.9:  Use ORIGINAL size of image.  This is important for complete transparency with spritesheet usage.
			return this.frames[frameIndex].origSize;
			//return this.frames[frameIndex].sourceSize;
		}
		else
		{
			var frameImage = this.getImageFrame(frameIndex);
			if (frameImage)
			{
				return { w: frameImage.width, h: frameImage.height };
			} else
			{	//	no image loaded?
				var debugName = this.debugName || "";
				rat.console.logOnce("r_image: error:  asking for size of image that hasn't been loaded : " + debugName, 'imageSizeError');
				return { w: 32, h: 32 };	//	fake it so we don't crash
			}
		}
	};

	//	set function to call when all frames are loaded
	rat.graphics.Image.prototype.setOnLoad = function (func)
	{
		this.onLoad = func;
		if( !func )
			return;
		//	already done?  I don't think this ever happens, even if image is cached.
		if (this.loadedCount >= this.loadedTargetCount)
		{
			//console.log("finished(pre) loading " + this.loadedCount + " images");
			this.onLoad(this.imageFrames[0]);
		}/* else
		{
			rat.console.log("waiting...");
		}
		*/
	};

	/**
	*	Draw.
	*	This hides internals, like whether we're single-frame, sprite-sheet, multi-sheet, etc.
	*
	*	IMPORTANT!
	*		This functionality is also extracted and rewritten in the particle system, for speed.
	*		Maybe not the best approach, since that has already broken at least once.
	*		As far as I can tell, it's to save one function call per draw.
	*		Anyway, if you change code here, you have to change it in the particle system, too.
	*		todo: Find some way to do macros instead of duplicating code?
	*
	*	@param {Object=} ctx
	*	@param {number=} frameNum
	*	@param {number=} x
	*	@param {number=} y
	*	@param {number=} w
	*	@param {number=} h
	*	@param {number=} flags
	*	@param {number=} clipX
	*	@param {number=} clipY
	*	@param {number=} clipWidth
	*	@param {number=} clipHeight
	*/
	rat.graphics.Image.prototype.draw = function (ctx, frameNum, x, y, w, h, flags, clipX, clipY, clipWidth, clipHeight)
	{
		ctx = ctx || rat.graphics.ctx;
		var offsetX;
		var offsetY;
		var frameImage;
		var offsetMultX = 1;
		var offsetMultY = 1;
		var saved = false;

		x = x || 0;
		y = y || 0;

		if (!this.frames)	//	easy version - single frame
		{
			frameImage = this.getImageFrame(frameNum);
			if (!frameImage)
				return;

			if (w === void 0)
				w = frameImage.width;
			if (h === void 0)
				h = frameImage.height;
			if (clipX === void 0)
				clipX = 0;
			if (clipY === void 0)
				clipY = 0;
			if (clipWidth === void 0)
				clipWidth = frameImage.width;
			if (clipHeight === void 0)
				clipHeight = frameImage.height;

			offsetX = 0;
			offsetY = 0;
			if (flags & rat.graphics.Image.centeredX)
				offsetX = -w / 2;
			if (flags & rat.graphics.Image.centeredY)
				offsetY = -h / 2;

			if (flags & rat.graphics.Image.flipX)
			{
				saved = true;
				ctx.save();
				offsetMultX = -1;
				ctx.translate(w, 0);
				ctx.scale(-1, 1);
			}
			if (flags & rat.graphics.Image.flipY)
			{
				if (!saved)
				{
					saved = true;
					ctx.save();
				}

				offsetMultY = -1;
				ctx.translate(0, h);
				ctx.scale(1, -1);
			}

			ctx.drawImage(frameImage, clipX, clipY, clipWidth, clipHeight, (x + offsetX) * offsetMultX, (y + offsetY) * offsetMultY, w, h);

		} else
		{	//	sprite sheet version (todo: consolidate common elements! yes...)
			// TODO! support clipping for sprite sheets - EG clipX, clipY, clipWidth, clipHeight

			frameImage = this.getImageFrame(0);
			if (!frameImage)
				return;

			var curFrame = this.frames[frameNum];

			if (typeof w === 'undefined')
				w = curFrame.origSize.w;	//	use original image size, as if this had never been packed in a sprite sheet
			if (typeof h === 'undefined')
				h = curFrame.origSize.h;

			//	figure out scale, and thus effective width and height in the trimmed "box" space...

			//var wscale = w / curFrame.sourceSize.w;	//	how much client wants to scale
			//var hscale = h / curFrame.sourceSize.h;
			var wscale = w / curFrame.origSize.w;	//	how much client wants to scale from original size
			var hscale = h / curFrame.origSize.h;
			var ew = curFrame.box.w * wscale;
			var eh = curFrame.box.h * hscale;

			offsetX = curFrame.trimRect.x * wscale;	//	account for trim
			offsetY = curFrame.trimRect.y * hscale;

			if (flags & rat.graphics.Image.flipX)
			{
				saved = true;
				ctx.save();
				offsetMultX = -1;
				ctx.translate(ew, 0);
				offsetX = (curFrame.origSize.w - (curFrame.trimRect.x + curFrame.box.w)) * wscale;
				ctx.scale(-1, 1);
			}
			if (flags & rat.graphics.Image.flipY)
			{
				if (!saved)
				{
					saved = true;
					ctx.save();
				}
				offsetMultY = -1;
				ctx.translate(0, eh);
				offsetX = (curFrame.origSize.h - (curFrame.trimRect.y + curFrame.box.h)) * hscale;
				ctx.scale(1, -1);
			}

			if (flags & rat.graphics.Image.centeredX)
				offsetX -= w / 2;	//	center based on desired render size, not trimmed image
			if (flags & rat.graphics.Image.centeredY)
				offsetY -= h / 2;

			ctx.drawImage(
				frameImage,
				curFrame.drawBox.x,	// use x in image
				curFrame.drawBox.y,	//	use y in image
				curFrame.drawBox.w,	//	use w in image
				curFrame.drawBox.h, // use h in image
				(x + offsetX) * offsetMultX,			//	Draw to x
				(y + offsetY) * offsetMultY,			//	Draw to y
				ew,											//	Draw to w
				eh);											//	Draw to h

			//	The image should draw in this frame
			//ctx.strokeRect(x, y, w*wscale, h*wscale);
		}

		if (saved)
			ctx.restore();

		//	for debugging purposes, count how many images we've drawn this frame.
		if (rat.graphics.frameIndex !== rat.graphics.Image.lastRenderFrameIndex)
		{
			rat.graphics.Image.lastRenderFrameIndex = rat.graphics.frameIndex;
			rat.graphics.Image.perFrameDrawCount = 0;
		}
		rat.graphics.Image.perFrameDrawCount++;
	};
	
	//------------------------------
	//	ImageRef class

	/** 
	    Image Ref
	    track a reference to an image, and track unique rendering info like current frame.
	    @constructor
	    @param {Array.<string>|string=} sources
	   
 */
	rat.graphics.ImageRef = function (sources)
	{
		//console.log("imageref constructor");

		if (typeof (sources) === 'object' && sources.isImageRef)	//	support copying from existing imageref
		{
			this.copyFrom(sources);
			return;

		} else if (typeof (sources) === 'object' && sources.isImage)	//	support construction from existing image object
		{
			this.image = sources;
		} else
		{
			this.image = new rat.graphics.Image();
			if (sources)
				this.image.appendFrames(sources);
		}

		//	note:  if you add fields to this list, remember to update copyFrom() below.
		//	this is an argument for using "flags" instead of adding separate named flag fields,
		//	since "flags" is already copied.
		
		this.frame = 0;
		this.animSpeed = 0; //	by default, not animated
		this.animDir = 1; //	by default, forward
		this.animPaused = false; // Is the animation paused

		//	todo move to a more flexible set of start/end action flags, e.g. stop at end, stop at start, autoreverse?
		//	use flags value below instead of named properties here.
		this.animAutoReverse = false;	//	if this is set, don't loop - reverse and bounce back and forth
		this.animOneShot = false;	//	assume looping, which is common.  oneShot means stop at end.
		//this.animStopAtEnd = false;	//	This is wrong.  We use "oneshot" flag instead.

		this.time = 0;

		this.flags = 0;
	};
	rat.graphics.ImageRef.prototype.isImageRef = true;	//	for ease of detection

	//	Set up all my values as a copy from this other imageRef's values.
	rat.graphics.ImageRef.prototype.copyFrom = function (source)
	{
		this.image = source.image;
		this.frame = source.frame;
		this.animSpeed = source.animSpeed;
		this.animDir = source.animDir;
		this.animPaused = source.animPaused;
		this.animAutoReverse = source.animAutoReverse;
		//this.animStopAtEnd = source.animStopAtEnd;
		this.animOneShot = source.animOneShot;
		this.time = source.time;
		this.flags = source.flags;
		this.frameEvents = source.frameEvents;
	};

	//	Assign the frame events
	rat.graphics.ImageRef.prototype.setFrameEvents = function (events) {
		this.frameEvents = events;
	};

	//	Fire any event assigned to the current frame
	rat.graphics.ImageRef.prototype.fireEventsForFrame = function (frame) {
		if (!this.frameEvents || !this.frameEvents[frame] || !rat.eventMap)
			return;
		rat.eventMap.fire(this.frameEvents[frame], this);
	};

	//
	//	given a sprite sheet, register it and build an imageref that uses all its frames.
	rat.graphics.ImageRef.buildFromSpriteSheet = function (res, tpjs)
	{
		rat.graphics.Image.registerSpriteSheet(res, tpjs);
		var imageRef = new rat.graphics.ImageRef();
		imageRef.image.buildFromSpriteSheet(tpjs);
		return imageRef;
	};

	//	given a sprite sheet already registered, build a collection of imagerefs with the proper animation frames,
	//	judging by frame name.
	//
	//	This is a very high level function intended to automatically build all the animations in a spritesheet,
	//	using the frame names to figure out what animations are distinct.
	//	Since we assume the spritesheet is already registered and loaded, don't worry about caching - just create all the imagerefs we need.
	//	Lots of assumptions about names, currently.  We assume names like "walk_0001.png"
	//	if there's no extension or the name is all numbers, this code will crash and burn.  I haven't bothered doing error checking yet.
	//
	//	This has been tested, and works in at least my test case, but it may have limited use.
	//	It doesn't tell us crucial things about the animations we collect, including animation speed.
	rat.graphics.ImageRef.constructAllFromSpriteSheet = function (tpjs)
	{
		//	get entry in our list of registered spritesheets
		var sheetIndex = rat.graphics.Image.getSpriteSheetIndex(tpjs);
		if (sheetIndex < 0)
			return null;

		var collection = {};

		function finishSequence(name, sequence)
		{
			if (sequence.length <= 0)
				return;

			//	trim trailing _ if any, for final name in our collection object
			var len = name.length;
			if (name.charAt(len - 1) === '_')
				name = name.substring(0, len - 1);

			var imageRef = new rat.graphics.ImageRef();
			imageRef.image.buildFramesFromSpriteSheets(sheetIndex, sequence);

			collection[name] = imageRef;

			//	add in both named and list form?
			//collection.list.push(imageRef);
		}

		//	walk through all the frames for that guy.  For now, let's maybe just assume they're sequential,
		//	and switch when the new one has a new name.
		var lastName = "";
		var sequence = [];
		var frames = rat.graphics.Image.spriteSheets[sheetIndex].tpjs[framesName];
		for (var frameIndex = 0; frameIndex < frames.length; frameIndex++)
		{
			var name = frames[frameIndex][fileNameName];
			//	examine that name.  deconstruct the numbering part with the base name part.
			//var len = name.length;
			var check = name.lastIndexOf(".");	//	skip past .png part
			name = name.substring(0, check);	//	cut that part off
			while (check >= 0)
			{
				if (isNaN(name.charAt(check)))	//	not a number
				{
					check++;	//	don't include bogus char in our number part
					break;
				}
				check--;
			}

			var number;
			if (check <= 0 || check >= name.length)	//	all number or no number part
				number = 0;
			else
			{
				var numPart = name.substring(check);
				name = name.substring(0, check);
				number = parseInt(numPart);
			}

			// OK!  Now, if the name changed, start a new list
			if (lastName !== name)
			{
				finishSequence(lastName, sequence);
				sequence = [];
			}

			//	add the original frame index to our existing list.
			sequence.push(frameIndex);
			lastName = name;
		}
		finishSequence(lastName, sequence);

		return collection;
	};

	rat.graphics.ImageRef.prototype.setOnLoad = function (func)
	{
		this.image.setOnLoad(func);
	};

	rat.graphics.ImageRef.prototype.isLoaded = function ()
	{
		return this.image.isLoaded();
	};

	//	get the current image, if it exists and is ready to draw.
	//	otherwise return null
	//	NOTE:  Don't call this if you want spritesheets to work transparently!
	//	This will return the raw image, e.g. the spritesheet in that case.
	rat.graphics.ImageRef.prototype.getImage = function ()
	{
		return this.image.getImageFrame(this.frame);
	};
	
	//	get a specific image frame, regardless of current known frame
	//	NOTE:  Don't call this if you want spritesheets to work transparently!
	//	This will return the raw image, e.g. the spritesheet in that case.
	rat.graphics.ImageRef.prototype.getImageFrame = function (index)
	{
		return this.image.getImageFrame(index);
	};

	//	Get the INDEX of a frame by name, if there is one with that name.
	//	otherwise return -1
	rat.graphics.ImageRef.prototype.getNamedFrame = function (name)
	{
		return this.image.getNamedFrame(name);
	};

	//	get frame count
	//	todo: inconsistent names - "frameCount()" should probably be "getFrameCount()" in image.
	rat.graphics.ImageRef.prototype.getFrameCount = function ()
	{
		return this.image.frameCount();
	};

	rat.graphics.ImageRef.prototype.setFrame = function (index)
	{
		var frameCount = this.image.frameCount();
		if (index < 0)
			index = 0;
		index = index % frameCount;	//	let's just wrap to make sure we're in the right range

		var oldFrame = this.frame;
		this.frame = index;

		if (oldFrame !== this.frame && this.frameEvents)
			this.fireEventsForFrame(this.frame);
	};

	//	set the current frame by name, if there's one with that name.
	rat.graphics.ImageRef.prototype.setFrameByName = function (name)
	{
		this.setFrame(this.getNamedFrame(name));
	};

	//	get current frame index
	rat.graphics.ImageRef.prototype.getFrame = function ()
	{
		return this.frame;
	};

	//	get size for this given frame
	//	returned object is in the form {w:..., h:...}
	rat.graphics.ImageRef.prototype.getFrameSize = function (frameIndex)
	{
		return this.image.getFrameSize(frameIndex);
	};

	//	get current frame size
	//	returned object is in the form {w:..., h:...}
	rat.graphics.ImageRef.prototype.getSize = function ()
	{
		return this.getFrameSize(this.frame);
	};

	rat.graphics.ImageRef.prototype.setAnimSpeed = function (animSpeed)
	{
		this.animSpeed = animSpeed;
	};
	rat.graphics.ImageRef.prototype.setAnimAutoReverse = function (autoReverse)
	{
		this.animAutoReverse = autoReverse;
	};
	rat.graphics.ImageRef.prototype.setAnimOneShot = function (oneShot)
	{
		this.animOneShot = oneShot;
	};
	rat.graphics.ImageRef.prototype.setAnimRandom = function (animRandom)
	{
		this.animRandom = animRandom;
	};
	/** @param {boolean=} paused */
	rat.graphics.ImageRef.prototype.setAnimPaused = function (pause)
	{
		this.animPaused = (pause === void 0) ? true : pause;
	};
	rat.graphics.ImageRef.prototype.setAnimFinished = function ()
	{
		this.time = this.image.frameCount();
		this.update(0);	//	immediately reflect this correctly in current frame, rather than wait for next update
	};
	
	//	Is this animation finished?  (Is it at or beyond the last frame?)
	//	TODO: a concern:  The *instant* the animation gets to the last frame, it will think it's done, even if there's only been a fraction of a second
	//	for that frame to be displayed.  Ideally, we would set a precise total *time* and use that instead, including showing the last frame as long as necessary.
	rat.graphics.ImageRef.prototype.isAnimFinished = function ()
	{
		return (this.time >= this.image.frameCount());
	};
	rat.graphics.ImageRef.prototype.isAnimOneShot = function ()
	{
		return this.animOneShot;
	};
	rat.graphics.ImageRef.prototype.restartAnim = function ()
	{
		this.time = 0;
		this.update(0);	//	immediately reflect this correctly in current frame, rather than wait for next update
	};
	
	//	set our current time to a random time in our animation.
	//	This is useful for making sure two things on the screen don't have synched up animations.
	rat.graphics.ImageRef.prototype.setRandomTime = function ()
	{
		this.time = Math.random() * this.image.frameCount();
		this.update(0);	//	immediately reflect this correctly in current frame, rather than wait for next update
	};

	//	update our state, mostly dealing with animation
	rat.graphics.ImageRef.prototype.update = function (dt)
	{
		if (this.animSpeed > 0)
		{
			var oldFrame = this.frame;
			
			var frameCount = this.image.frameCount();
			if (!this.animPaused)
			{
				this.time += this.animSpeed * this.animDir * dt;
				var timeLen = frameCount;
				
				if (this.animAutoReverse)	//	bounce back and forth
				{
					if (this.time > timeLen)
					{
						var delta = this.time - timeLen;	//	go back as much as we went over
						this.time = timeLen - delta;
						this.animDir = -1;
					}
					if (this.time < 0)
					{
						if (this.animOneShot)
						{
							this.time = 0;	//	Stay at the front.
						}
						else
						{
							this.time = -this.time;	//	go forward as much as we went too far back
							this.animDir = 1;
						}
					}
				} else if (this.animOneShot)
				{
					if (this.time > timeLen)
					{
						this.time = timeLen;	//	stay at end
					}
				}
				else if (this.animRandom)	//	pick random frame
				{
					if (this.time >= 1)
					{
						this.time -= 1;	//	start over for next frame
						
						//	special case: frame and time are not the same thing.
						//	so, don't do the logic below - just directly set the frame.
					
						//	pick a random frame that's not the one we're on!
						var addFrame = (Math.random() * (frameCount-1))|0 + 1;
						this.frame = (this.frame + addFrame) % frameCount;
						
						//	annoying that I copied this from below, but I'm about to skip all that,
						//	and I still want this behavior...
						if (this.frameEvents && oldFrame !== this.frame)
							this.fireEventsForFrame(this.frame);
					}
					//	don't make all the frame == time assumptions below!
					return;
					
				} else	//	default: loop
				{	//	loop back to start
					while (this.time > timeLen)	//	could have gone way past end, need to keep looping back
					{
						this.time -= timeLen; //	back to start, factoring in how much we overshot
					}
				}
			}

			//	update frame based on time
			//	(being paused but animating doesn't mean that someone else isn't changing our time explicitly.  Let them.)
			this.frame = (this.time) | 0;

			//	clamp to valid space
			if (this.frame < 0)
				this.frame = 0;
			if (this.frame >= frameCount)
				this.frame = frameCount - 1;

			if (this.frameEvents && oldFrame !== this.frame)
				this.fireEventsForFrame(this.frame);
		}
		//	otherwise leave it alone - maybe somebody manually controls current frame
	};

	/**
	*	draw!  See image draw above.  This just picks correct frame.
	*	@param {Object=} ctx
	*	@param {number=} x
	*	@param {number=} y
	*	@param {number=} w
	*	@param {number=} h
	*	@param {number=} clipX
	*	@param {number=} clipY
	*	@param {number=} clipWidth
	*	@param {number=} clipHeight
	*/
	rat.graphics.ImageRef.prototype.draw = function (ctx, x, y, w, h, clipX, clipY, clipWidth, clipHeight)
	{
		this.image.draw(ctx, this.frame, x, y, w, h, this.flags, clipX, clipY, clipWidth, clipHeight);
	};

	rat.graphics.ImageRef.prototype.setCentered = function (centeredX, centeredY)
	{
		if (typeof centeredY === 'undefined')
			centeredY = centeredX;
		if (centeredX)
			this.flags |= rat.graphics.Image.centeredX;
		else
			this.flags &= ~rat.graphics.Image.centeredX;
		if (centeredY)
			this.flags |= rat.graphics.Image.centeredY;
		else
			this.flags &= ~rat.graphics.Image.centeredY;

		//	Allow chaining calls
		return this;
	};

	rat.graphics.ImageRef.prototype.setFlipped = function (flipX, flipY)
	{
		if (flipX)
			this.flags |= rat.graphics.Image.flipX;
		else
			this.flags &= ~rat.graphics.Image.flipX;
		if (flipY)
			this.flags |= rat.graphics.Image.flipY;
		else
			this.flags &= ~rat.graphics.Image.flipY;
	};

	//--------------- Caching images -----------------

	//	this is a cache of images, in the form of rat.graphics.Image obects.
	rat.graphics.imageCache = [];
	rat.graphics.imageCache.leftToLoad = 0;

	//	explicitly preload (start caching) a bunch of images.
	//	The argument here can be either a single image
	//		rat.graphics.preLoadImages("bob.png");
	//	or a list
	//		rat.graphics.preLoadImages(["bob.png", "sally.png", "frank.png"]);
	//	This will start those images loading, and register them in our internal cache for easy use later.
	//	More importantly, when you try to use them later, they'll be loaded and have width/height info available.
	//	You can check if preloading is done by calling isCacheLoaded below.
	//	You can call this as many times as you want.
	rat.graphics.preLoadImages = function (list)
	{
		if (!Array.isArray(list))	//	for convenience, allow a single string to be passed in
			list = [list];

		//	we skip an image's preload if it's already in spritesheets or the cache.
		
		var alreadyInSheetsCount = 0;
		var alreadyCachedCount = 0;
		//console.log("preloading this many images: " + list.length + "\n");
		for (var i = 0; i < list.length; i++)
		{
			if (rat.graphics.Image.findInSpriteSheet([list[i]]))
			{
				alreadyInSheetsCount++;
				continue;
			}
			
			if (rat.graphics.findInCache(list[i]))
			{
				alreadyCachedCount++;
				continue;
			}

			rat.graphics.imageCache.leftToLoad += 1;
			
			//console.log( "    preloading image " + (i+1) + " of " + list.length + "\n" );
			//	just add each one as a rat.graphics.Image
			var image = new rat.graphics.Image();
			image.appendFrames(list[i]);
			image.setOnLoad( function(image, data)
			{
				image.setOnLoad(void 0);
				--rat.graphics.imageCache.leftToLoad;
				if( rat.graphics.imageCache.leftToLoad < 0 )
					rat.graphics.imageCache.leftToLoad = 0;
			}.bind(void 0, image));
			rat.graphics.imageCache.push(image);

			//console.log("    precaching " + list[i]);
		}
		//console.log("precaching " + rat.graphics.imageCache.leftToLoad + " images, "
		//		+ alreadyInSheetsCount + " were already in sheets, "
		//		+ alreadyCachedCount + " already cached.");
		//console.log("done queueing up cache.");
	};
	
	//	directly add this image to the cache, assuming it's already loaded.
	//	the "imageToAdd" here can be a rat image, or an image resource name (which had better already be loaded/cached!) or a canvas.
	rat.graphics.addToCache = function(imageToAdd, useName)
	{
		//	see if we're replacing an entry in the cache.
		//	TODO: why isn't the cache a hash instead of an array?
		var toIndex = rat.graphics.imageCache.length;
		var replaceImage;	//	if there is one
		if (useName)
		{
			var findInfo = {};
			replaceImage = rat.graphics.findInCache(useName, findInfo);
			if (replaceImage)
			{
				replaceImage = rat.graphics.imageCache[findInfo.index];	//	reference the rat image instead.
				toIndex = findInfo.index;
			}
		}
		
		if (!imageToAdd.isImage)	//	if it's not a rat image, make it one.
		{
			//	If we're *replacing* an image, don't make a whole new image object.
			//	(it might have other properties we want to keep)
			if (replaceImage)
			{
				replaceImage.imageFrames[0] = imageToAdd;
				imageToAdd = replaceImage;	//	imageToAdd now refers to a rat.graphics.Image
			} else {
				var imageSrc = imageToAdd;
				imageToAdd = new rat.graphics.Image();
				imageToAdd.appendFrames(imageSrc);
			}
			//	todo: adding "origSrc" property here seems pretty questionable.
			//	assuming index 0 also seems pretty questionable.
			//	Need to revamp this whole system and only have single images in the cache, of a low-level simple type.
			if (useName)
				imageToAdd.imageFrames[0].origSrc = rat.utils.cleanPath( useName );
			imageToAdd.imageFrames[0].valid = true;	//	assume already fully loaded.
		}
		
		//	write to the slot we determined above
		rat.graphics.imageCache[toIndex] = imageToAdd;
	};
	
	//	Remove an image from the cache
	rat.graphics.clearFromCache = function(path)
	{
		var foundOut = {};
		var found = rat.graphics.findInCache(path, foundOut);
		if( !found )
			return;
		
		//	Remove from the cache
		rat.graphics.imageCache.splice( foundOut.index, 1 );
		
		//	This may be over kill, but i want to make sure that the image is gone
		//	Because the image object in the cache is shared with rat.graphics.images that
		//	Use it, i cannot modify the actual image object.
		//found.cleanup();
	};
	
	//	see if entire cache is loaded (e.g. match call to preLoadImages above)
	rat.graphics.isCacheLoaded = function ()
	{
		for (var i = 0; i < rat.graphics.imageCache.length; i++)
		{
			if (!rat.graphics.imageCache[i].isLoaded())
			{
				//console.log("image cache waiting for " + rat.graphics.imageCache[i].imageFrames[0].src);
				return false;
			}
		}
		return true;
	};
	
	//	get cache state
	//	todo: update this only when things change, not every time called.
	rat.graphics.getCacheState = function()
	{
		var res = {
			total : rat.graphics.imageCache.length,
			current : 0,
			firstWaiting : -1,
		};
		
		for (var i = 0; i < rat.graphics.imageCache.length; i++)
		{
			if (rat.graphics.imageCache[i].isLoaded())
				res.current++;
			else if (res.firstWaiting < 0)
				res.firstWaiting = i;
		}
		
		return res;
	};

	//	Report what imgaes are in the cache
	rat.graphics.reportCache = function ()
	{
			var cached = rat.graphics.imageCache;
			rat.console.log( "-----------------------------------" );
			rat.console.log( "" + cached.length + " images in cache." );
			for (var i = 0; i < cached.length; i++)
			{
				//	todo	don't call getImageFrame at all?
				//	it's annoying that it doesn't let us debug invalid frames.
				var img = cached[i].getImageFrame(0, { noWarn: true });
				var srcText;
				if (img)
					srcText = img.origSrc;
				else if (cached[i].debugName)
					srcText = cached[i].debugName;
				else
					srcText = "[failed setup]";
				rat.console.log( "\t " + i + " > " + srcText );
			}
			rat.console.log( "-----------------------------------" );
	};
	rat.console.registerCommand( "imageCache", rat.graphics.reportCache, ["reportImageCache", "showImageCache"] );
	
	//	see if this image is already in cache
	//	return low level Image directly, if found.
	//	otherwise, return null.
	//	STT: adding canvas support, so "resource" here could be asset string or canvas object
	//	TODO: make the imageCache a hash instead of list,
	//		so findInCache is faster.
	//		If we're ever searching for a canvas by object, then do it the old way - search through the whole cache, I guess?
	rat.graphics.findInCache = function (resource, extraInfoOut)
	{
		resource = rat.utils.cleanPath( resource );
		
		//console.log("find in cache " + resource);
		//console.log("cache is this long: " + rat.graphics.imageCache.length);
		for (var i = 0; i < rat.graphics.imageCache.length; i++)
		{
			/** 	todo:  We currently assume all frames are cached as single frames,
			  	but will that be the case?  Do we need to walk through all frames?
			  	note: not calling getImageFrame because it does a bunch of extra checking,
			  	including returning null if the image isn't loaded yet, but it's OK if the image is not loaded,
			  	we're just trying to say whether the image is even registered in the cache,
			  	not whether we can draw it or its size or anything.
			  	was:  var img = rat.graphics.imageCache[i].getImageFrame(0, { noWarn: true });
 */
			
			var entry = rat.graphics.imageCache[i];
				
			if (entry.imageFrames && entry.imageFrames.length > 0)
			{
				var img = entry.imageFrames[0];
				if (img !== null)
				{
					//console.log("checking " + img.origSrc);
					if ((img.origSrc && img.origSrc === resource) || img === resource)
					{
						if (extraInfoOut)	//	if they asked for it, return additional image info (e.g. index in cache)
							extraInfoOut.index = i;
						return img;
					}
				}
			}
		}
		//console.log("not found: " + resource);
		return null;
	};

	//---------------- support for external image systems to use our api ------------------

	rat.graphics.externalSpriteMaker = null;

	//	a sort of light factory.  Make images.
	//	if we have an external image creator and extra arguments were supplied, then use that creator.
	//	todo: support variable args instead of fixed 2.
	rat.graphics.makeImage = function (resource, extra1, extra2)
	{
		if (extra1 && rat.graphics.externalSpriteMaker)
		{
			return rat.graphics.externalSpriteMaker(resource, extra1, extra2);
		} else
		{
			return new rat.graphics.ImageRef(resource);
		}
		
	};

	//	set up an external sprite creation function.
	//	This is basically for bridging other image-handling libraries in to rat.
	//		or providing a way to have very different image creation support on a game by game basis.
	//	Any image created with this external sprite maker (see makeImage() above!) must respect the same basic API
	//		that we support with ImageRef, particularly draw(), update(), getFrameSize(), isLoaded(), and more.
	//		see usage in r_ui_sprite
	rat.graphics.setExternalSpriteMaker = function (f)
	{
		rat.graphics.externalSpriteMaker = f;
	};
});
//--------------------------------------------------------------------------------------------------
//
//	Particle system
//
//	Basic idea:
//		particle systems are objects, so you can have more than one particle system in existence.
//		system contains emitters
//		emitters manage particles
//
//

//	TO DO:
//		* finish start/various/end state blending ideas
//		* allow user to tag emitters for later debugging or finding?
//		* general tag system - in fact, just assume everything has a "tag" variable?  no need for special per-class code.
//		* once we know the type of an emitter, set its draw function so we don't do branching in draw loop,
//			though this will have function call overhead
//		* custom draw function (combined with the above)
//		* specific support for flashups (text? or any type?) (dedicated emitter, created if needed?)
//		* for particles that are not changing color each frame, store color in string form so we don't convert every time we draw.
//			that's a lot of converting.
//

//	NOTE: Variance is generally assumed to be +/- value.
//	e.g. a variance of 10 means from -10 to +10
rat.modules.add( "rat.graphics.r_particle",
[
	{name: "rat.math.r_math", processBefore: true},
	
	"rat.debug.r_console",
	"rat.debug.r_profiler",
	"rat.graphics.r_graphics",
	"rat.math.r_vector",
	"rat.graphics.r_image"
], 
function(rat)
{
	var math = rat.math;
	//var clamp = math.clamp;
	//var Interp = math.interp;
	var RandomVariance = math.randomVariance;
	
	rat.particle = {
		systems: [],
		stateCaching: {
			enabled: true,		//	Will rat cache the particle state objects
			minObjectCount: 0,	// If caching state objects, the system will create this number of state objects to populate the cached when you launch the game
			createPerFrame: 0	// If rat is trying to meet the minObjectCount for the state objects, how many will we create per frame.  0 = all.  If not zero, rat.cycleUpdate is required
		},
	};

	//===============================================
	//------------- particle systems ----------------
	//===============================================

	/**
	 * @constructor
	 */
	rat.particle.System = function (options)	//	constructor for particle system
	{
		this.options = options;
		this.emitters = [];
	};

	//	Some event style callbacks for adding an emitter, removeing an emitter and creating an emitter
	rat.particle.System.prototype.onRemoveEmitter = void 0;
	rat.particle.System.prototype.onAddEmitter = void 0;
	rat.particle.System.prototype.emitterConstructor = void 0;

	//	particle system constants
	rat.particle.System.infinite = -1;
	rat.particle.System.statusDead = 0;
	rat.particle.System.statusActive = 1;
	rat.particle.System.statusAlive = 1;

	rat.particle.System.emitTypeUnknown = 0;
	rat.particle.System.emitTypeSingle = 1;
	rat.particle.System.emitTypeBurst = 2;
	rat.particle.System.emitTypeStream = 3;

	rat.particle.System.killParticles = true;	//	argument to killAllEmitters() below

	rat.particle.System.RENDER_UNKNOWN = 0;	//	hasn't been correctly set up yet.
	rat.particle.System.RENDER_DOT = 1;
	rat.particle.System.RENDER_TRIANGLE = 2;
	rat.particle.System.RENDER_BOX = 3;
	rat.particle.System.RENDER_SPRITE = 4;
	rat.particle.System.RENDER_TEXT = 5;
	rat.particle.System.RENDER_CUSTOM = 10;

	//-------------
	//	create new emitter in this system
	rat.particle.System.prototype.newEmitter = function ()
	{
		var Ctor = this.emitterConstructor || rat.particle.Emitter;
		var emitter = new Ctor();
		this.emitters.push(emitter);
		if (this.options && this.options.trackBounds)
			emitter.trackBounds = this.options.trackBounds;
		
		//alert("ecount: " + this.emitters.length);
		if (this.onAddEmitter)
			this.onAddEmitter(emitter);
		return emitter;
	};

	//-------------
	//	return total emitter count in this system
	rat.particle.System.prototype.getEmitterCount = function ()
	{
		return this.emitters.length;
	};

	//-------------
	//	kill all emitters in this system
	rat.particle.System.prototype.killAllEmitters = function (killParticles)
	{
		if(killParticles === void 0)
			killParticles = true;	//	let's assume you usually want to clear everything out...
		for(var i = this.emitters.length - 1; i >= 0; i--)
		{
			this.emitters[i].die(killParticles);
		}
	};

	//-------------
	//	kill all particles in this whole system without killing the emitters.
	//	e.g. if they're still emitting, there will be more soon.
	//	NOTE:  You might want killAllEmitters() above, instead.
	rat.particle.System.prototype.killAllParticles = function ()
	{
		for(var i = this.emitters.length - 1; i >= 0; i--)
		{
			this.emitters[i].killAllParticles();
		}
	};

	//-------------
	//	move this emitter to the top visually (last in list)
	rat.particle.System.prototype.moveEmitterToTop = function (emitter)
	{
		for(var i = this.emitters.length - 1; i >= 0; i--)
		{
			if(this.emitters[i] === emitter)
			{
				this.emitters.splice(i, 1);
				this.emitters.push(emitter);
				return;
			}
		}
	};
	
	//-------------
	//	remove this emitter from the system.
	//	Note that you generally want something else, like to mark an emitter as dead.
	//	This is a specialized function for yanking an emitter out of the system (not deleting it)
	//	useful for things like storing it and putting it back in later, or putting it in another system.
	rat.particle.System.prototype.removeEmitter = function (emitter)
	{
		for(var i = this.emitters.length - 1; i >= 0; i--)
		{
			if(this.emitters[i] === emitter)
			{
				if (this.onRemoveEmitter)
					this.onRemoveEmitter(this.emitters[i]);
				this.emitters.splice(i, 1);
				return;
			}
		}
	};
	
	//-------------
	//	Explicitly append this existing emitter to this system's list.
	//	This is a specialized function that assumes correct creation of the emitter.
	//	Generally, you want newEmitter() instead.
	rat.particle.System.prototype.appendEmitter = function (emitter)
	{
		this.emitters.push(emitter);
		if (this.onAddEmitter)
			this.onAddEmitter(emitter);
	};

	//-------------
	//
	//	Update all emitters in this system
	//
	rat.particle.System.prototype.update = function (dt)
	{
		rat.profiler.pushPerfMark("PSystem:Update");
		for(var i = this.emitters.length - 1; i >= 0; i--)
		{
			var status = this.emitters[i].update(dt);
			if(status === rat.particle.System.statusDead)
			{
				this.emitters[i].destroy();
				if (this.onRemoveEmitter)
					this.onRemoveEmitter(this.emitters[i]);
				this.emitters.splice(i, 1);
			}
		}
		rat.profiler.popPerfMark("PSystem:Update");
	};

	//-------------
	//	Render this entire particle system, all emitters and particles.
	rat.particle.System.prototype.draw = function (options)
	{
		rat.profiler.pushPerfMark("PSystem:draw");
		var useCtx;
		if (options && options.ctx)
			useCtx = options.ctx;
		else
			useCtx = rat.graphics.getContext();
		var oldCtx = rat.graphics.getContext();
		rat.graphics.setContext(useCtx);
		rat.graphics.save({ ignoreRatMat: true });
		
		for(var i = 0; i < this.emitters.length; i++)
		{
			this.emitters[i].draw(useCtx);
		}
		
		rat.graphics.restore();
		rat.graphics.setContext(oldCtx);
		rat.profiler.popPerfMark("PSystem:draw");
	};
	
	//-------------
	//	translate all my emitter bounds to rectangle list (e.g. dirtyRects list) info.
	rat.particle.System.prototype.applyBoundsToRectList = function(rectList)
	{
		for(var i = 0; i < this.emitters.length; i++)
		{
			var e = this.emitters[i];
			if (e.trackBounds)
			{
				//if (e.prevBounds.w > 0 && e.prevBounds.h > 0)
				//	rectList.add(e.prevBounds);
				if (e.bounds.w > 0 && e.bounds.h > 0)
					rectList.add(e.bounds);
			}
		}
	};
	//	old deprecated name
	rat.particle.System.prototype.applyToDirtyRects = rat.particle.System.prototype.applyBoundsToRectList;

	//=======================================
	//------------- emitters ----------------
	//=======================================

	//-------------
	//	emitter constructor
	//var createdEmitters = 0;
	/**
	 * @constructor
	 */
	rat.particle.Emitter = function ()
	{
		//rat.console.log( "Created " + ( ++createdEmitters ) + " Emitters!" );
		this.pos = new rat.Vector(100, 100);	//	default, should be set externally
		this.angle = new rat.Angle();	//	default create our own, may be replaced with ref to some external angle
		this.startState = rat.particle.State.create();	//	to be set up externally, we presume.
		this.startVariance = rat.particle.State.create();	//	empty, default 0 values, no variance!

		this.asset = [];
		this.stateSets = [];	//	list of states and variances {state: state, variance: variance}
		this.stateSets[0] = { state: this.startState, variance: this.startVariance };

		//	by default, end and start the same, and if one changes, change the other.  If client wants end state unique,
		//	then need to do something like myEmitter.endState = myEmitter.startState.copy() or call initEndState...
		//	Note:  this is less and less useful?  It may just be a shortcut for clients at this point.
		this.endState = this.startState;

		//	some more reasonable start values, for debugging ease, mostly, so client doesn't have to be perfect to see anything...
		//this.startState.color.setWhite();	//	includes 1.0 alpha, which is important for ease of setup
		this.startState.color.a = 1.0;
		this.startState.color.r = 255;
		this.startState.color.g = 255;
		this.startState.color.b = 255;
		this.startState.ageLimit = 1;

		//	prepare to track total bounds of my particles, if requested.
		this.prevBounds = {x : this.pos.x, y : this.pos.y, w : 0, h : 0};
		this.bounds = {x : this.pos.x, y : this.pos.y, w : 0, h : 0};
		this.trackBounds = false;	//	but assume it's not desired, yet

		this.particles = [];	//	list of particles, starting empty
	};

	//	flags for emitters and particles
	//	These flags control both emitter behavior and particle behavior,
	//	since it's optimal to store as much data as we can (like flags) in the emitter instead of in each particle.
	rat.particle.Emitter.fAutoDie = 0x0001;					//	auto die emitter if we're ever out of particles
	rat.particle.Emitter.fAutoDieAfterAgeLimit = 0x0004;	//	after emitter hitting age limit, set autodie flag (probably usually desired, if age limit)
	rat.particle.Emitter.fRadialStartVelocity = 0x0008;		//	start velocity is radial instead of x/y
	rat.particle.Emitter.fRadialStartOffset = 0x0010;		//	start position offset is radial distance instead of x/y
	rat.particle.Emitter.fEmitting = 0x0100;				//	start/stop emitting
	rat.particle.Emitter.fEmitImmediately = 0x0200;			//	start timer advanced to emit point
	rat.particle.Emitter.fStroke = 0x0800;					//	text: stroke
	rat.particle.Emitter.fGlobalVelocity = 0x1000;			//	global velocity for initial particles, not emitter relative
	rat.particle.Emitter.fGlobalOffset = 0x2000;			//	global offset for initial particle position, not emitter relative
	rat.particle.Emitter.fKillParticleAfterAnimation = 0x4000;		//	kill particle after animation finishes (assuming oneshot animation, and sprite, etc.)

	// Initial state of emitters.   ONLY PUT VALUES THAT ARE COPIED BY VALUE, NOT REFERENCE(like objects)
	rat.particle.Emitter.prototype.flags = rat.particle.Emitter.fEmitting;	// behavior flags: by default, no autodie, etc.
	rat.particle.Emitter.prototype.emitType = rat.particle.System.emitTypeStream;
	rat.particle.Emitter.prototype.renderType = rat.particle.System.RENDER_UNKNOWN;	//	by default, render boxes
	rat.particle.Emitter.prototype.rate = 10.0;		//	emit/burst per second (default, should be set externally)
	rat.particle.Emitter.prototype.burstCount = 1;	//	for burst types, how many times to burst, usually 1
	rat.particle.Emitter.prototype.burstAmount = 0;	//	for burst types, how much to burst at once
	rat.particle.Emitter.prototype.emitCounter = 0;	//	current counter (time) to next emit time
	rat.particle.Emitter.prototype.status = rat.particle.System.statusActive;	//	start alive
	rat.particle.Emitter.prototype.ageLimit = rat.particle.System.infinite;	//	emitter last forever by default
	rat.particle.Emitter.prototype.age = 0;	//	current age (time)
	rat.particle.Emitter.prototype.font = "";
	rat.particle.Emitter.prototype.fontSize = 0;
	rat.particle.Emitter.prototype.createEvent = null;	//	event (function) to call for each particle on create:  f(emitter, particle)
	rat.particle.Emitter.prototype.updateEvent = null;	//	event (function) to call for each particle on update
	rat.particle.Emitter.prototype.deathEvent = null;		//	event (function) to call for each particle on death
	rat.particle.Emitter.prototype.customDraw = null;		//	function to call to draw particle, if type is set to CUSTOM
	rat.particle.Emitter.prototype.isReadyForUse = false;//	so we know to do some optimizations/calculations before using for the first time

	//-------------
	//	set/clear flags
	rat.particle.Emitter.prototype.setFlag = function (flag)
	{
		this.flags |= flag;
	};
	rat.particle.Emitter.prototype.clearFlag = function (flag)
	{
		this.flags &= ~flag;
	};
	rat.particle.Emitter.prototype.isFlag = function (flag)
	{
		return ((this.flags & flag) !== 0);
	};

	//-------------
	//	start/stop emitting
	rat.particle.Emitter.prototype.startEmitting = function ()
	{
		this.setFlag(rat.particle.Emitter.fEmitting);
	};
	rat.particle.Emitter.prototype.stopEmitting = function ()
	{
		this.clearFlag(rat.particle.Emitter.fEmitting);
	};
	rat.particle.Emitter.prototype.isEmitting = function ()
	{
		return this.isFlag(rat.particle.Emitter.fEmitting);
	};

	//-------------
	//	Kill all my particles instantly.
	//	But leave me in whatever state I was in (e.g. still emitting)
	rat.particle.Emitter.prototype.killAllParticles = function ()
	{
		//	Cache the states from my particles
		if(rat.particle.stateCaching.enabled)
		{
			for(var index = 0, len = this.particles.length; index !== len; index++)
			{
				if(this.particles[index].destroy)
					this.particles[index].destroy();
			}
		}
		this.particles = [];
	};

	//-------------
	//	die - stop emitting, and delete emitter from system when particles are dead.
	//	that could be done manually by client, but this function makes it easy and obvious.
	rat.particle.Emitter.prototype.die = function (killParticles)
	{
		this.stopEmitting();
		this.setFlag(rat.particle.Emitter.fAutoDie);
		if(killParticles)
			this.killAllParticles();//	no more particles - will die next update because of autodie flag.
	};

	//-------------
	//	add an intermediate state
	//	keyTime and keyFlags can be undefined, in which case a defaults are assigned when readyForUse ends up getting called, eventually.
	//	See readyForUse() for more info.
	//	note:  keyTime here is actually an interpolation value from 0 to 1, not a literal time in seconds.
	//		also, keyTime is a START point for the given state.  state lengths are calculated automatically later.
	rat.particle.Emitter.prototype.addState = function (keyTime, keyFlags)
	{
		var newState = this.stateSets[this.stateSets.length - 1].state.copy();
		newState.keyTime = keyTime;
		newState.keyFlags = keyFlags;
		var entry = { state: newState, variance: rat.particle.State.create() };
		this.stateSets.push(entry);

		//rat.console.log("rpe: addstate " + this.stateSets.length);

		return entry;
	};

	//-------------
	//	get full initial state set with variance and key values and stuff
	rat.particle.Emitter.prototype.getInitState = function ()
	{
		return this.stateSets[0];
	};

	//-------------
	//
	//	Make the endstate different from the start state
	//	Convenience function, might make more sense to an outsider than doing things by hand.
	//
	rat.particle.Emitter.prototype.initEndState = function (keyTime, keyFlags)
	{
		var entry = this.addState(keyTime, keyFlags);
		this.endState = entry.state;
		return entry;
	};

	//-------------
	//	set up for standard burst
	rat.particle.Emitter.prototype.setupStandardBurst = function (amount)
	{
		this.emitType = rat.particle.System.emitTypeBurst;
		this.rate = 1;	//	not important for burst
		this.emitCounter = 1 / this.rate + 0.0001;	//	advance counter to do it right away at next update
		this.burstCount = 1;	//	burst once
		this.burstAmount = amount;	//	count per burst
	};

	//-------------
	//	return total number of particles being handled by this emitter
	rat.particle.Emitter.prototype.getParticleCount = function ()
	{
		return this.particles.length;
	};

	//-------------
	//	do some cleanup, calculations, etc. that can happen one time after setup and before use,
	//	to avoid doing it every frame, or on every setup call (e.g. flag setting)
	rat.particle.Emitter.prototype.readyForUse = function ()
	{
		//var	lastState;
		var state;
		var i;
		for(i = 0; i < this.stateSets.length; i++)
		{
			state = this.stateSets[i].state;

			//	put in key times if they weren't specified.
			//	These are spread evenly out, from 0 to 1.  E.g. if you have 4 keys (including start and end) you get 0.0, 0.33, 0.66, 1.0
			//	if a given key time has been specified, we leave it, and just set the others.
			if(typeof (state.keyTime) === 'undefined')
				state.keyTime = i / (this.stateSets.length - 1);	//	by default, even key times
			if(typeof (state.keyFlags) === 'undefined')
				state.keyFlags = 0;

			//	check if a numeric angle was specified...
			if(typeof(state.angle) !== 'object')
				state.angle = new rat.Angle(state.angle);
			
			//	detect if colors change over time at all.  Eventually, do this with other animated state vars.

			//	TODO:  Hmmm!  If the user sets an initial color with variance,
			//		and then doesn't want to override that calculated color later, how does he do so?  We kinda need something like "undefined" values
			//		for state variables, to mean "leave it like it is".
			//		This would apply to colors, and also things like angle.
			//			If an angle is defined in each key, then fine, interpolate (animate).  If not, then leave angle (or let it roll or whatever).

			//lastState = {state:this.state
		}
		//	check my own angle
		if(typeof(this.angle) !== 'object')
			this.angle = new rat.Angle(this.angle);

		//	calculate the length of time allocated to each key for convenience later
		//	(figure each one but the last one, which is always 0)
		for(i = 0; i < this.stateSets.length - 1; i++)
		{
			state = this.stateSets[i].state;
			state.keyTimeLength = this.stateSets[i + 1].state.keyTime - state.keyTime;
			//rat.console.log("key tl[" + i + "] " + state.keyTimeLength);
		}
		this.stateSets[this.stateSets.length - 1].state.keyTimeLength = 0;
		//	unless there's only 1 key, in which case its time length is simply 1
		if(this.stateSets.length === 1)
			this.stateSets[this.stateSets.length - 1].state.keyTimeLength = 1;
		//rat.console.log("key tl[" + (this.stateSets.length-1) + "] " + this.stateSets[this.stateSets.length-1].state.keyTimeLength);
		
		if (this.isFlag(rat.particle.Emitter.fEmitImmediately))
			this.emitCounter = 1 / this.rate + 0.0001;	//	advance counter to spawn right away on next update
		
		this.isReadyForUse = true;
	};

	//-------------
	//
	//	Update emitter, updates the emitter's particles
	//	return status.
	//
	rat.particle.Emitter.prototype.update = function (dt)
	{
		if(this.renderType === rat.particle.System.RENDER_UNKNOWN)	//	the emitter has not been set up.  Just kill it.
			return rat.particle.System.statusDead;

		var emitterStatus;

		rat.profiler.pushPerfMark("Emitter.update");

		if(!this.isReadyForUse)
			this.readyForUse();
		var i;
		
		if (this.trackBounds)
		{
			this.prevBounds = this.bounds;
			this.bounds = {x:this.pos.x, y:this.pos.y, w:0, h:0};	//	todo: hrm... set to some clearly identified "not set yet" value instead?  is pos legit here?
		}

		if(this.rate > 0 && this.isEmitting())	//	if we're emitting, handle emission at our rate
		{
			this.emitCounter += dt;
			if(this.emitCounter > 1 / this.rate)
			{
				rat.profiler.pushPerfMark("Emitting");
				//	reset counter
				this.emitCounter -= 1 / this.rate;	//	don't just set to 0, factor in how far we actually progressed
				/** @todo support emitting more if the rate was faster than our DT.  Right now we're assuming 1.
				   @todo support retroactively putting the particles in the right place, retroactively, and setting their life correctly, etc...
				   @todo support applying emit rate variance every time counter is calculated again.
 */

				//	emit, depending on type
				if(this.emitType === rat.particle.System.emitTypeStream)
				{
					this.spawnNewParticle();

				} else if(this.emitType === rat.particle.System.emitTypeBurst && this.burstCount > 0)
				{
					for(i = 0; i < this.burstAmount; i++)
					{
						this.spawnNewParticle();
					}
					this.burstCount--;
					if(this.burstCount <= 0)
					{
						this.rate = 0;	//	done with our burst count, don't burst any more
						this.stopEmitting();	//	just in case, but generally not used with burst emitters?
					}
				}
				rat.profiler.popPerfMark("Emitting");
			}
		}

		//	Update my particles, including deleting dead ones.
		rat.profiler.pushPerfMark("p.update");
		var curP;
		for (i = this.particles.length - 1; i >= 0; i--)
		{
			curP = this.particles[i];
			var status = curP.update(dt, this);
			if (status === rat.particle.System.statusDead)
			{
				if (this.deathEvent)	//	is there a registered create event function to call?
				{
					this.deathEvent(this, curP);
				}
				
				if (this.particles[i].destroy )
					this.particles[i].destroy();
				this.particles.splice(i, 1);
			} else {	//	not dead
				//	track bounds.  We could be rotated, so be generous.
				//	but rotation worst case will add sqrt(2)... or about 1.414,
				//	thus the 1.5 values below.
				//	and we subtract half that for x/y pos
				if (this.trackBounds)
				{
					this.addToBounds(curP.state.pos.x - curP.state.size*0.75, curP.state.pos.y - curP.state.size*0.75,
							curP.state.size * 1.5, curP.state.size * 1.5);
				}

				/*	this should all be handled in update() call above.
				//	this is hardcoded for now... start/end states only.
				//	heck, let's just hack colors and a few other things for now...
				var interp = curat.particle.age / curat.particle.ageLimit;
				interp = clamp(interp, 0, 1);
				curat.particle.color.a = Interp(this.startState.color.a, this.endState.color.a, interp);
			
				//	would also be nice to animate emitter values like rate...!
				//	think about more general animation systems for sets of values?  an animated keyframed set of values just spits out
				//	automatically into a new struct, and people just access the struct?  Don't overdo it... 
			
				//	update stuff
				curat.particle.angle.angle += curat.particle.roll * dt;
				*/
			}
			
		}
		rat.profiler.popPerfMark("p.update");

		/** @todo	check for particle death event support
		   @todo	check for particle update event support
 */

		//	check for emitter age death
		rat.profiler.pushPerfMark("statusUpdate");
		this.age += dt;
		//	age limit stops an emitter from emitting when it has reached this age
		if(this.ageLimit > 0 && this.age > this.ageLimit)
		{
			//this.rate = 0;	//	stop spawning
			this.stopEmitting();	//	use new flag instead so isEmitting can be used to detect this.

			if(this.flags & rat.particle.Emitter.fAutoDieAfterAgeLimit)
				this.flags |= rat.particle.Emitter.fAutoDie;	//	now, autodie when our particles are finished

			//	maybe support a flag that instantly kills on age limit?
			//return rat.particle.System.statusDead;
		}

		//	check for emitter autodeath if no particles are left.
		//	note that this happens after emitter checks above, so a newly created emitter has a chance to create particles first.
		if(this.flags & rat.particle.Emitter.fAutoDie)
		{
			if(this.particles.length <= 0)
			{
				//rat.console.log("*** autodie!");
				emitterStatus = rat.particle.System.statusDead;
			}
		}
		else
		{
			emitterStatus = rat.particle.System.statusActive;
		}
		rat.profiler.popPerfMark("statusUpdate");

		//	by default, we're still alive
		rat.profiler.popPerfMark("Emitter.update");
		return emitterStatus;
	};

	//-------------
	//
	//	Destroy this emitter
	//
	rat.particle.Emitter.prototype.destroy = function ()
	{
		this.killAllParticles();
		if(rat.particle.stateCaching.enabled)
		{
			for(var index = 0, len = this.stateSets.length; index !== len; ++index)
			{
				rat.particle.State.destroy(this.stateSets[index].state);
				rat.particle.State.destroy(this.stateSets[index].variance);
			}
			this.stateSets = [];
		}
	};

	//-------------
	//
	//	Spawn new particle from this emitter
	//
	rat.particle.Emitter.prototype.spawnNewParticle = function ()
	{
		rat.profiler.pushPerfMark("spawnNewParticle");

		var particle = new rat.particle.One();
		var asset;
		var rad;

		//	OK, let's generate this particle's various state variables right now, based on emitter's state list and variances
		var sets = this.stateSets;
		var state, base, variance;
		rat.profiler.pushPerfMark("setupStates");
		for(var i = 0, len = sets.length; i !== len; ++i)
		{

			state = rat.particle.State.create();
			//	for easy access in particle later - could use emitter's values, but this leads to shorter code
			base = sets[i].state;
			variance = sets[i].variance;

			//	build the state
			state.keyTime = base.keyTime;
			state.keyTimeLength = base.keyTimeLength;
			state.size = base.size;
			state.grow = base.grow;
			state.roll = base.roll;
			state.friction = base.friction;
			state.color.r = base.color.r;
			state.color.g = base.color.g;
			state.color.b = base.color.b;
			state.color.a = base.color.a;

			//	Handle variance of the state fields
			if(variance.size)
				state.size += (variance.size * 2 * math.random() - variance.size);
			if(variance.grow)
				state.grow += (variance.grow * 2 * math.random() - variance.grow);
			if(variance.roll)
				state.roll += (variance.roll * 2 * math.random() - variance.roll);
			if(variance.friction)
				state.friction += (variance.friction * 2 * math.random() - variance.friction);
			if(variance.color.r)
			{
				state.color.r += (variance.color.r * 2 * math.random() - variance.color.r);
				state.color.r = ((state.color.r < 0) ? 0 : ((state.color.r > 255) ? 255 : (state.color.r | 0)));
			}
			if(variance.color.g)
			{
				state.color.g += (variance.color.g * 2 * math.random() - variance.color.g);
				state.color.g = ((state.color.g < 0) ? 0 : ((state.color.g > 255) ? 255 : (state.color.g | 0)));
			}
			if(variance.color.b)
			{
				state.color.b += (variance.color.b * 2 * math.random() - variance.color.b);
				state.color.b = ((state.color.b < 0) ? 0 : ((state.color.b > 255) ? 255 : (state.color.b | 0)));
			}
			if(variance.color.a)
			{
				state.color.a += (variance.color.a * 2 * math.random() - variance.color.a);
				state.color.a = ((state.color.a < 0) ? 0 : ((state.color.a > 1) ? 1 : state.color.a));
			}

			// This state is part of the particle
			particle.states[i] = state;
		}
		rat.profiler.popPerfMark("setupStates");

		//	and set initial "now" state
		rat.profiler.pushPerfMark("initialState");
		particle.state.size = particle.states[0].size;
		particle.state.grow = particle.states[0].grow;

		// was this, but sometimes we failed to receive a proper new Color object, so setting this manually for now

		//particle.state.color.copyFrom(particle.states[0].color);
		particle.state.color.a = particle.states[0].color.a;
		particle.state.color.r = particle.states[0].color.r;
		particle.state.color.g = particle.states[0].color.g;
		particle.state.color.b = particle.states[0].color.b;

		particle.state.friction = particle.states[0].friction;
		particle.state.roll = particle.states[0].roll;

		//	Set a bunch of properties for the new particle based on emitter flags, startState, and startVariance.
		//	These properties don't animate based on keyframes - they're initialized here and modified over time with
		//	unique logic, like acceleration being gravity...

		//	start at emitter position
		//	ref...
		//particle.state.pos = new rat.Vector(this.pos.x, this.pos.y)
		//	pos already exists, and might be being tracked.  Don't create new one.
		particle.state.pos.x = this.pos.x;
		particle.state.pos.y = this.pos.y;
		//	note that emitter pos may not be a Vector, depending on client usage

		//	offset based on emitter properties
		var offset = new rat.Vector();
		if(this.flags & rat.particle.Emitter.fRadialStartOffset)	//	radial space for offset
		{
			//	find a random radial vector, and then scale it with just the X value
			rad = math.random() * math.PI2;
			offset.setFromAngle(rad);
			offset.scale(this.startState.offset.x + RandomVariance(this.startVariance.offset.x));
		} else
		{
			offset.x = this.startState.offset.x + RandomVariance(this.startVariance.offset.x);
			offset.y = this.startState.offset.y + RandomVariance(this.startVariance.offset.y);
		}
		if(!this.isFlag(rat.particle.Emitter.fGlobalOffset))
			offset = this.angle.rotateVector(offset);
		particle.state.pos.x += offset.x;
		particle.state.pos.y += offset.y;

		particle.state.angle.angle = this.angle.angle;//	start by matching emitter angle
		//	then add state settings
		particle.state.angle.angle += this.startState.angle.angle + RandomVariance(this.startVariance.angle.angle);
		//	todo: support a flag to use absolute angles instead of emitter relative?

		if(this.flags & rat.particle.Emitter.fRadialStartVelocity)	//	radial space
		{
			//	so, how do we do this?
			//	find a random radial vector, and then scale it with just the X value and x variance...?
			rad = math.random() * math.PI2;
			particle.state.vel = new rat.Vector();
			particle.state.vel.setFromAngle(rad);
			particle.state.vel.scale(this.startState.vel.x + RandomVariance(this.startVariance.vel.x));

		} else
		{	//	normal square space
			particle.state.vel = new rat.Vector(
					this.startState.vel.x + RandomVariance(this.startVariance.vel.x)
					, this.startState.vel.y + RandomVariance(this.startVariance.vel.y)
					);	//	in units (pixels) per second
		}

		particle.state.accel = new rat.Vector(
				this.startState.accel.x + RandomVariance(this.startVariance.accel.x)
				, this.startState.accel.y + RandomVariance(this.startVariance.accel.y)
				);	//	in units (pixels) per second per second

		//	rotate calculated vel to match emitter angle at this instant.
		if(!this.isFlag(rat.particle.Emitter.fGlobalVelocity))
			particle.state.vel = this.angle.rotateVector(particle.state.vel);
		//	note: do NOT rotate accel, which is assumed to be in world space, not emitter space.
		//	if we need support for that later, we can add it.  TODO:  relative accel, also radial accel!

		particle.ageLimit = this.startState.ageLimit + RandomVariance(this.startVariance.ageLimit);
		rat.profiler.popPerfMark("initialState");

		//	set up particle asset reference, e.g. sprite for sprite particles.
		//	for now, if asset hasn't loaded yet, set null asset.
		particle.asset = null;
		if(this.renderType === rat.particle.System.RENDER_SPRITE)
		{
			asset = this.asset;
			if(this.assetSpawnFunction)
			{
				asset = this.assetSpawnFunction(this);
			}

			if(Array.isArray(asset))	//	allow the emitter's asset to be a list of assets
				asset = asset[(math.random() * asset.length) | 0];

			particle.setImageAsset(asset);

		} else if(this.renderType === rat.particle.System.RENDER_TEXT)
		{
			//	for text, asset is string (or list of strings)
			asset = this.asset;
			if(Array.isArray(this.asset))
				asset = this.asset[(math.random() * this.asset.length) | 0];
			particle.text = asset;
		}

		if(this.createEvent)	//	is there a registered create event function to call?
		{
			rat.profiler.pushPerfMark("createEvent");
			var res = this.createEvent(this, particle);
			rat.profiler.popPerfMark("createEvent");
			if(res === false)
			{
				//	OK, just kidding!  don't add this to our list.  Lose it.
				rat.profiler.popPerfMark("spawnNewParticle");
				return;
			}
		}
		
		//	track space used (add to bounds)
		//	see other call to addToBounds for notes.
		if (this.trackBounds)
		{
			this.addToBounds(particle.state.pos.x - particle.state.size*0.75, particle.state.pos.y - particle.state.size*0.75,
					particle.state.size * 1.5, particle.state.size * 1.5);
		}

		//	add to my particle list
		this.particles.push(particle);

		rat.profiler.popPerfMark("spawnNewParticle");
	};
	
	//-------------
	//	util to add a point (with space) to the bounds we're tracking.
	rat.particle.Emitter.prototype.addToBounds = function (x, y, w, h)
	{
		//	faster approach to this math?  Or store differently?
		//	This seems unoptimal...
		if (x < this.bounds.x)
		{
			this.bounds.w += (this.bounds.x - x);
			this.bounds.x = x;
		}
		if (y < this.bounds.y)
		{
			this.bounds.h += (this.bounds.y - y);
			this.bounds.y = y;
		}
		if (x + w > this.bounds.x + this.bounds.w)
			this.bounds.w = x + w - this.bounds.x;
		if (y + h > this.bounds.y + this.bounds.h)
			this.bounds.h = y + h - this.bounds.y;
	};
	
	//-------------
	//	explicitly spawn N new particles from this emitter.
	rat.particle.Emitter.prototype.spawn = function (count)
	{
		if(typeof (count) === 'undefined')
			count = 1;
		for(var i = 0; i < count; i++)
		{
			this.spawnNewParticle();
		}
	};

	//	Utility function to calculate start value +/- variance for a named member of the particle structure
	//	This function accesses normal object properties by string name,
	//	which confuses the google compiler.
	//	So... let's try this a different way.  Ugh, can't come up with a better way.
	//	I'm replacing all use of this function for now... see above.
	// intead do: this.foo.startState.bar + RandomVariance(this.foo.startVariance.bar);
	//rat.particle.Emitter.prototype.startWithVar = function(field)
	//{
	//	return this.startState[field] + RandomVariance(this.startVariance[field]);
	//}

	//-------------
	//
	//	Draw my particles.
	//
	rat.particle.Emitter.prototype.draw = function (ctx)
	{
		rat.profiler.pushPerfMark("Emitter.Draw");

		if(this.preDraw)
			this.preDraw(ctx, this);
		var scale;

		//	setup that's universal for all particles
		var stroke = false;
		if(this.renderType === rat.particle.System.RENDER_TEXT)
		{
			ctx.textBaseline = 'middle';
			ctx.textAlign = 'center';
			if(this.flags & rat.particle.Emitter.fStroke)
				stroke = true;
		}

		//	actual draw for each particle
		rat.profiler.pushPerfMark("particle.Draw");
		for(var i = 0; i < this.particles.length; i++)
		{
			
			//	if this ever gets too slow, maybe collapse the draw function into this one?
			//	would make polymorphism a lot harder...  probably won't be slow.
			//	Hmm...  actually, we really should have the behavior of these particles determined by the emitter.
			//	so, the draw should be here anyway, even if it's a separate function, which may not be necessary.
			//this.particles[i].draw(ctx);

			//	context save/restore is necessary so we can rotate below and have it be relative to particle
			//	Although...  An alternative might be to rotate, and then rotate back.
			//	this context save/restore is theoretically expensive.
			//	Also, we could first test if rotate is being used?
			var p = this.particles[i];
			var ps = p.state;
			//	Bypass the rat gfx and directly access the context here.
			ctx.save();
			ctx.translate(ps.pos.x, ps.pos.y);
			
			//	temp test...
			/*
			if (ps.pos.x < this.bounds.x || ps.pos.y < this.bounds.y
				|| ps.pos.x > this.bounds.x + this.bounds.w
				|| ps.pos.y > this.bounds.y + this.bounds.h)
			{
				console.log("ugh");
			}
			*/
			
			if(ps.angle.angle)
				ctx.rotate (ps.angle.angle);
			//rat.graphics.save();
			//rat.graphics.translate(ps.pos.x, ps.pos.y, ctx);
			//if(ps.angle.angle)
			//	rat.graphics.rotate(ps.angle.angle, ctx);

			/** @todo	switch this to function reference or lambda, to avoid check every time through loop...
 */
			if(this.renderType === rat.particle.System.RENDER_BOX)
			{
				scale = ps.size;
				ctx.fillStyle = ps.color.toString();//"#6040FF";
				ctx.fillRect(-scale / 2, -scale / 2, scale, scale);

			} else if(this.renderType === rat.particle.System.RENDER_TRIANGLE)	//	maybe generic RENDER_POLYGON instead
			{
				var radius = ps.size / 2;
				var rotInc = math.PI2 / 3;	//	3 sides
				ctx.fillStyle = ps.color.toString();

				ctx.beginPath();

				ctx.moveTo(radius, 0);
				ctx.lineTo(math.cos(rotInc) * radius, math.sin(rotInc) * radius);
				ctx.lineTo(math.cos(2 * rotInc) * radius, math.sin(2 * rotInc) * radius);

				ctx.closePath();
				ctx.fill();

			} else if(this.renderType === rat.particle.System.RENDER_DOT)
			{
				ctx.fillStyle = ps.color.toString();//"#6040FF";
				ctx.beginPath();
				scale = ps.size / 2;	//	radius! (half size)
				ctx.arc(0, 0, scale, 0, math.PI * 1.9999, false);
				ctx.closePath();
				ctx.fill();

			} else if(this.renderType === rat.particle.System.RENDER_SPRITE)
			{
				if(p.asset)	//	if particle has valid asset reference, use that.
				{
					ctx.globalAlpha = ps.color.a;

					//	scale to the size of the particle, not the image size.
					//	(these scaleWidth factors are usually 1, but can be something else if the image is not square)
					var sw = ps.size * p.scaleWidth;
					var sh = ps.size * p.scaleHeight;

					if(p.asset.isImageRef)
					{
						//	The following code is an extracted and optimized version of the draw call.
						/** /p.asset.draw(ctx, 0, 0, sw, sh);
						  p.asset.image.draw(ctx, p.asset.frame, 0, 0, sw, sh, p.asset.flags);
 */
						var img = p.asset.image;
						var frameNum = p.asset.frame;
						var flags = p.asset.flags;
						var sheetStyle = !!(img.frames);	//	is this a spritesheet style image?
						var frameImage = img.getImageFrame(sheetStyle ? 0 : frameNum);
						var offsetX = 0;
						var offsetY = 0;
						//	easy version - single frame
						if(frameImage)
						{
							if(!sheetStyle)
							{
								if(flags & rat.graphics.Image.centeredX)
									offsetX = -sw / 2;
								if(flags & rat.graphics.Image.centeredY)
									offsetY = -sh / 2;
								ctx.drawImage(frameImage, offsetX, offsetY, sw, sh);
								
								//	temp debug - show space we're drawing in
								//ctx.strokeStyle = "#FF80FF";
								//ctx.lineWidth = 2;
								//ctx.strokeRect(offsetX, offsetY, sw, sh);
							}
							else
							{
								var curFrame = img.frames[frameNum];

								//	adapt w and h to relative w/h based on trimmed size
								
								//	first, how much does client want to scale from the original source image size?
								var wscale = sw / curFrame.origSize.w;
								var hscale = sh / curFrame.origSize.h;
								//	and, since we'll be drawing from a smaller space in the sheet, if the image was trimmed,
								//	then figure out effective width of that...
								var ew = curFrame.box.w * wscale;
								var eh = curFrame.box.h * hscale;

								offsetX = curFrame.trimRect.x * wscale;	//	account for trim
								offsetY = curFrame.trimRect.y * hscale;
								if(flags & rat.graphics.Image.centeredX)
									offsetX -= sw / 2;	//	center based on desired render size, not trimmed image
								if(flags & rat.graphics.Image.centeredY)
									offsetY -= sh / 2;
								ctx.drawImage(frameImage, curFrame.drawBox.x, curFrame.drawBox.y, curFrame.drawBox.w, curFrame.drawBox.h, offsetX, offsetY, ew, eh);
							}
						}
					}
					else
					{
						ctx.drawImage(p.asset, 0, 0, p.asset.width, p.asset.height, -sw / 2, -sh / 2, sw, sh);
					}
				}

			} else if(this.renderType === rat.particle.System.RENDER_TEXT)
			{
				ctx.font = (ps.size * this.fontSize) + 'pt ' + this.font;//(p.size * this.fontSize) + this.font;
				//	or?
				//rat.graphics.scale(p.size * this.fontSize, ctx);

				if(stroke)
				{
					//	todo - support second color per particle for this kind of thing
					//	and it needs to be animated, fade, etc...
					//	for now, stroke at a much darker color of the same
					ctx.strokeStyle = "rgba(" + (ps.color.r >> 3) + "," + (ps.color.g >> 3) + "," + (ps.color.b >> 3) + "," + ps.color.a + ")";
					ctx.lineWidth = 3;
					ctx.strokeText(p.text, -ps.size / 2, 0);
				}
				ctx.fillStyle = ps.color.toString();
				ctx.fillText(p.text, -ps.size / 2, 0);

			} else if(this.renderType === rat.particle.System.RENDER_CUSTOM)
			{
				//	don't draw.  See customDraw call below
				//	I'm making that separate so you can have a custom draw in addition to build-in draw.
			} else
			{
				//alert("unknown render type");
				rat.console.log("Error:  Unknown particle render type");
			}

			if(this.customDraw)
			{
				var emitter = this;
				this.customDraw(ctx, p, emitter);	//	note: custom draw happens in translated/rotated space	
			}

			//rat.graphics.restore();
			ctx.restore();
		}
		rat.profiler.popPerfMark("particle.Draw");
		
		//	temp debug - show emitter location
		//ctx.fillStyle = "#FFFF80";
		//ctx.fillRect(this.pos.x - 10, this.pos.y - 10, 20, 20);

		if(this.postDraw)
			this.postDraw(ctx, this);

		rat.profiler.popPerfMark("Emitter.Draw");
	};

	//-------------
	//	convenience function to set additive pre/post draw functions
	rat.particle.Emitter.prototype.setAdditive = function ()
	{
		this.preDraw = rat.particle.Emitter.preDrawAdditive;
		this.postDraw = rat.particle.Emitter.postDrawAdditive;
	};

	//	some standard useful predraw stuff
	rat.particle.Emitter.preDrawAdditive = function (ctx, emitter)
	{
		emitter.oldOperation = ctx.globalCompositeOperation;
		ctx.globalCompositeOperation = 'lighter';
	};
	rat.particle.Emitter.postDrawAdditive = function (ctx, emitter)
	{
		ctx.globalCompositeOperation = emitter.oldOperation;
	};

	//===========================================================================
	//-------------- classes for individual particles and states ----------------
	//===========================================================================

	//	state variables - these are initial, intermediate, or end states for a particle,
	//	as well as a particle's current state,
	//	or variance values for those same states.
	//var createdStates = 0;
	/**
	 * @constructor
	 */
	rat.particle.State = function ()
	{
		//rat.console.log( "Created " + ( ++createdStates ) + " Particle States!" );
		this.pos = new rat.Vector();
		this.offset = new rat.Vector();
		this.angle = new rat.Angle();
		this.vel = new rat.Vector();
		this.accel = new rat.Vector();
		this.color = new rat.graphics.Color(0, 0, 0, 0);
		this.protoObject = rat.particle.State.prototype;
	};
	rat.particle.State.prototype.size = 0;
	rat.particle.State.prototype.grow = 0;
	rat.particle.State.prototype.roll = 0;
	rat.particle.State.prototype.ageLimit = 0;
	rat.particle.State.prototype.friction = 0;
	rat.particle.State.prototype.nextInCache = void 0;
	/**
	 * Reset this state object as though it had just been newed
	 */
	rat.particle.State.prototype.reset = function()
	{
		// By val types.
		this.size = this.protoObject.size;
		this.grow = this.protoObject.grow;
		this.roll = this.protoObject.roll;
		this.ageLimit = this.protoObject.ageLimit;
		this.friction = this.protoObject.friction;
		this.nextInCache = void 0;
		
		//	we depend on keyTime and keyLength starting undefined,
		//	so we can know if the user has set them explicitly.
		//	That was undocumented, and the new state caching broke that.  My fault, really. (STT)
		//	let's try this to reset them to undefined...
		//	Alternatively, we could set up new values that mean "undefined", like -1
		var uu;	//	undefined
		this.keyTime = uu;
		this.keyFlags = uu;

		//	Objects created during the initialization process
		this.pos.x = 0;
		this.pos.y = 0;
		this.offset.x = 0;
		this.offset.y = 0;
		this.angle.angle = 0;
		this.vel.x = 0;
		this.vel.y = 0;
		this.accel.x = 0;
		this.accel.y = 0;
		this.color.r = 0;
		this.color.g = 0;
		this.color.b = 0;
		this.color.a = 0;
	};
	rat.particle.State.prototype.copy = function ()
	{
		var p = rat.particle.State.create();

		//	copy all atomic variables automatically
		// JHS This destroys the different vectors/angles/colors and makes us re-create the
		//for (var e in this) {
		//  p[e] = this[e];
		//}
		//	Simple By-Val copy
		p.size = this.size;
		p.grow = this.grow;
		p.roll = this.roll;
		p.ageLimit = this.ageLimit;
		p.friction = this.friction;
		p.keyTime = this.keyTime;
		p.keyFlags = this.keyFlags;

		//	Complex types need to be copied field by field
		p.pos.x = this.pos.x;
		p.pos.y = this.pos.y;
		p.offset.x = this.offset.x;
		p.offset.y = this.offset.y;
		p.angle.angle = this.angle.angle;
		p.vel.x = this.vel.x;
		p.vel.y = this.vel.y;
		p.accel.x = this.accel.x;
		p.accel.y = this.accel.y;
		p.color.r = this.color.r;
		p.color.g = this.color.g;
		p.color.b = this.color.b;
		p.color.a = this.color.a;

		return p;
	};
	/** The cache of state objects for the particle system */
	rat.particle.State.cache = void 0;
	/** The number of cached objects */
	rat.particle.State.cacheSize = 0;
	/** The of state objects that exist */
	rat.particle.State.count = 0;

	/**  JHS Cache of state objects
	  *
	 * Get a new state object, either from the cache or create one
	 */
	rat.particle.State.create = function ()
	{
		if(rat.particle.State.cache)
		{
			var state = rat.particle.State.cache;
			rat.particle.State.cache = state.nextInCache;
			--rat.particle.State.cacheSize;
			state.reset();

			return state;
		}
		else
		{
			++rat.particle.State.count;
			return new rat.particle.State();
		}
	};
	/**
	 * Destroy a state object, and cache it.
	 */
	rat.particle.State.destroy = function (state)
	{
		if(state)
		{
			state.nextInCache = rat.particle.State.cache;
			rat.particle.State.cache = state;
			++rat.particle.State.cacheSize;
		}
	};

	/**
	 * Called once per frame in an attempt to fill the cache with some state objects to avoid hits to our framerate when creating lots of particles
	 * @param {number} deltaTime
	 */
	rat.particle.State.FillCache = function (deltaTime)
	{
		//	Create some state objects
		var leftToCreate = rat.particle.stateCaching.minObjectCount - rat.particle.State.count;
		if(leftToCreate > 0)
		{
			//	How many are we going to create?
			var howManyToCreate = math.min(leftToCreate, rat.particle.stateCaching.createPerFrame);
			for(var left = howManyToCreate; left > 0; --left)
			{	
				rat.particle.State.destroy(new rat.particle.State()); // If i use create, i get it from the cache I am trying to fill which is NOT what i want.
				++rat.particle.State.count;
			}
			leftToCreate -= howManyToCreate;
		}

		if( leftToCreate <= 0 )
		{
			//	We don't need to update anymore
			if( rat.cycleUpdate )
				rat.cycleUpdate.removeUpdater(rat.particle.State.FillCache);
		}
	};

	//
	//	A single particle
	//	This has a current state ("this.state"), and its own copy of a state array for interpolation over time.
	//	This is so we can calculate states with variance values from the emitter, but then stick to those calculated
	//	values when interpolating.
	//
	/**
	 * @constructor
	 */
	rat.particle.One = function ()
	{
		this.state = rat.particle.State.create();	//	my current state
		this.states = [];	//	my list of state keys (may be refs to emitter's state keys, if possible) (no variances)
		this.age = 0;
		this.ageLimit = 0;

		//	set up refs for convenience and external access?
		this.pos = this.state.pos;//new rat.Vector();	//	why not in "state"? maybe for easier external access
		//this.angle = this.state.angle;

		this.curKeyIndex = 0;	//	if we have state keyframes, start with first one
	};

	//	This may currently be unused?
	rat.particle.One.prototype.copy = function ()
	{
		var p = new rat.particle.One();

		//	copy all atomic variables automatically
		// JHS Again, this destroy vector and states forcing us to re-create them
		for(var e in this)
		{
			p[e] = this[e];
		}

		//	some things (complex types) need to be copied explicitly
		p.pos = this.pos.copy();
		p.state = this.state.copy();

		//	copy list of states
		var numStates = this.states.length;
		p.states = [];
		for(var i = 0; i < numStates; i++)
		{
			p.states[i] = this.states[i].copy();
		}

		return p;
	};

	//	single particle update function
	rat.particle.One.prototype.update = function (dt, e)
	{
		var s = this.state;

		s.size += s.grow * dt;
		if(s.size < 0)
			s.size = 0;

		//	decay velocity because of friction, if any (should be from emitter?)
		var vel = s.vel;
		var fric = s.friction;
		if(fric > 0)
		{
			fric = 1.0 - fric * dt;
			vel.x *= fric;
			vel.y *= fric;
		}

		//	apply new acceleration, if any (should be from emitter?)
		var accel = s.accel;
		vel.x += accel.x * dt;
		vel.y += accel.y * dt;

		//	apply velocity to position
		var pos = s.pos;
		pos.x += vel.x * dt;
		pos.y += vel.y * dt;

		//	roll
		s.angle.angle += s.roll * dt;

		//	interp some values based on keyframes.
		//	figure out how far long in time we are, and find the two appropriate keyframes to use
		//	todo:  curve interp somehow - maybe just use ease in/out
		//	todo:  optimize?  See note about skipping calculations below.
		//	todo:  optimize: skip all this calculation if there ARE no state keys.  Just keep our state and move on.  that happens often enough, right?

		var interp;	//	interp between appropriate key states
		var keyInterp = this.age / this.ageLimit;	//	total interp over life
		//keyInterp = clamp(interp, 0, 1);
		var stateCount = this.states.length;
		var segs = stateCount - 1;

		//starting with the current segment, see if our time is past the next segment's key time start,
		//	and if so, move our "current segment" marker up...
		for(var segIndex = this.curKeyIndex + 1; segIndex < segs; segIndex++)
		{
			if(keyInterp >= this.states[segIndex].keyTime)
				this.curKeyIndex = segIndex;
		}

		//var indexA = math.floor(segs * keyInterp);	//	this didn't allow for custom key timing
		var indexA = this.curKeyIndex;
		var indexB = indexA + 1;
		if(indexB > segs)
		{
			indexB = indexA;
			interp = 0;
		} else
		{
			//	calculate how far past A and toward B we are
			interp = (keyInterp - this.states[indexA].keyTime) / this.states[indexA].keyTimeLength;
			interp = ((interp < 0) ? 0 : ((interp > 1) ? 1 : interp));
		}

		//	Currently, this is the only thing that animates, really, for particles.
		//	todo - detect at setup if colors are ever changing, and skip calculations if not (set flag)
		//	See readyForUse function above, which would be a good time to do that.
		var invIVal = 1 - interp;
		var from = this.states[indexA];
		var to = this.states[indexB];
		if(to.color.r !== from.color.r) {
			var r = to.color.r * interp + invIVal * from.color.r;
			r = ((r < 0) ? 0 : ((r > 255) ? 255 : (r | 0)));
			s.color.r = r;
		} else {
			s.color.r = to.color.r;
		}
		if(to.color.g !== from.color.g) {
			var g = to.color.g * interp + invIVal * from.color.g;
			g = ((g < 0) ? 0 : ((g > 255) ? 255 : (g | 0)));
			s.color.g = g;
		} else {
			s.color.g = to.color.g;
		}
		if(to.color.b !== from.color.b) {
			var b = to.color.b * interp + invIVal * from.color.b;
			b = ((b < 0) ? 0 : ((b > 255) ? 255 : (b | 0)));
			s.color.b = b;
		} else {
			s.color.b = to.color.b;
		}
		if(to.color.a !== from.color.a) {
			var a = to.color.a * interp + invIVal * from.color.a;
			a = ((a < 0) ? 0 : ((a > 1) ? 1 : a));
			s.color.a = a;
		}else {
			s.color.a = to.color.a;
		}
				
		//	would also be nice to animate emitter values like rate...!
		//	think about more general animation systems for sets of values?  an animated keyframed set of values just spits out
		//	automatically into a new struct, and people just access the struct?  Don't overdo it... 

		var status = rat.particle.System.statusActive;
		
		if(this.asset && this.asset.isImageRef)
		{
			this.asset.update(dt);
			if ((e.flags & rat.particle.Emitter.fKillParticleAfterAnimation) && this.asset.animOneShot && this.asset.isAnimFinished())
				status = rat.particle.System.statusDead;
		}

		this.age += dt;

		//	todo: support explicitly saying particles live forever (and will get killed some other way!)
		//	maybe with ageLimit -1 like other places in particle system?
		if(this.age >= this.ageLimit)
		{
			status = rat.particle.System.statusDead;
		}
		
		return status;
	};

	/**
	 *	Set the rendering asset for this one particle to be this image.
	 * @param {?} asset
	 * @param {Object=} emitter
	 */
	rat.particle.One.prototype.setImageAsset = function (asset, emitter)
	{
		if (asset.isImageRef)
		{
			//	OK, half the point of imageRef is to support light references to images that can have their own
			//	animation timing!  So, let's do this right, and copy the imageref.
			//	todo: support (through emitter flags) starting at a random point in sprite animation?
			this.asset = new rat.graphics.ImageRef(asset);
			//particle.assetIsImageRef = true;
			var size = asset.getSize();
			//	remember some values for proper scaling
			this.scaleWidth = 1;	//	by default, match width
			this.scaleHeight = size.h / size.w;

		} else
		{	//	normal image
			if(asset.width > 0 && asset.height > 0)	//	loaded and ready?
			{
				this.asset = asset;
				//	remember some values for proper scaling
				this.scaleWidth = 1;	//	by default, match width
				this.scaleHeight = asset.width / asset.height;
			}
		}
	};

	/*
	see above - currently we let the emitter draw its particles, so we don't have the overhead of a function call.
	rat.particle.One.prototype.draw = function(ctx)
	{
		//ctx.fillStyle = "rgba(100, 100, 200," + this.color.a + ")";
		ctx.fillStyle = this.color.toString();//"#6040FF";
		ctx.beginPath();
		var scale = 5;
		ctx.arc(this.pos.x, this.pos.y, scale, 0, math.PI * 2, true);
		ctx.closePath();
		ctx.fill();
	}
	*/

	/**
	 * Cleanup this single particle
	 */
	rat.particle.One.prototype.destroy = function ()
	{
		rat.particle.State.destroy(this.state);
		this.state = void 0;

		for(var i = 0, len = this.states.length; i !== len; ++i)
		{
			rat.particle.State.destroy(this.states[i]);
		}
		this.states = [];
	};

	//	system utility functions

	rat.particle.createSystem = function (options)
	{
		var ps = new rat.particle.System(options);
		rat.particle.systems[rat.particle.systems.length] = ps;
		return ps;
	};
	
	//	createSystem adds to a master list, which could mean references sticking around...
	//	So if you call createSystem, you probably eventually want to call removeSystem as well.
	rat.particle.removeSystem = function (sys)
	{
		var totalSystems = rat.particle.systems.length;
		for(var i = 0; i < totalSystems; i++)
		{
			if (rat.particle.systems[i] === sys)
			{
				rat.particle.systems.splice(i, 1);
				return;
			}
		}
	};

	rat.particle.getSystemCount = function () { return rat.particle.systems.length; };

	rat.particle.getAllEmitterCount = function ()
	{
		var totalEmitters = 0;
		var totalSystems = rat.particle.systems.length;
		for(var i = 0; i < totalSystems; i++)
		{
			var emitterCount = rat.particle.systems[i].getEmitterCount();
			totalEmitters += emitterCount;
		}
		return totalEmitters;
	};

	rat.particle.getAllParticleCount = function ()
	{
		var totalSystems = rat.particle.systems.length;
		var totalParticles = 0;
		for(var i = 0; i < totalSystems; i++)
		{
			var emitterCount = rat.particle.systems[i].getEmitterCount();
			for(var j = 0; j < emitterCount; j++)
			{
				totalParticles += rat.particle.systems[i].emitters[j].getParticleCount();
			}
		}
		return totalParticles;
	};

	/**
	 * Initialize the particle system
	 * @param {Object=} options used to setup the particle system
	 */
	rat.particle.init = function (options)
	{
		//	Are we caching state objects?
		if(rat.particle.stateCaching.enabled)
		{
			//	Create them all?
			if(rat.particle.stateCaching.createPerFrame <= 0)
			{
				rat.particle.stateCaching.createPerFrame = rat.particle.stateCaching.minObjectCount;
				rat.particle.State.FillCache(0);
				rat.particle.stateCaching.createPerFrame = 0;
			}
			//	Create them over time?
			if(rat.cycleUpdate)
			{
				rat.cycleUpdate.addUpdater(rat.particle.State.FillCache);
			}
		}
		else
		{
			rat.particle.stateCaching.minObjectCount = 0;
			rat.particle.stateCaching.createPerFrame = 0;
		}		
	};
} );
//--------------------------------------------------------------------------------------------------
/*

	rat.Offscreen
	
	This is an offscreen buffer (canvas) that you can draw to once (or at least less frequently)
	and then render into a normal scene.
	
	This is useful for performance optimization, among other things.
	
	Simple Use Case 1:
		* create an Offscreen
		* draw to it once
		* render to your scene each frame after that
		
		var off = new rat.Offscreen(200, 200);
		var ctx = off.getContext();
		ctx.fillStyle = "#FF0000";
		ctx.fillRect(0, 0, 200, 200);
		...
		off.render(mainCTX);
		
	Use Case 2:
		* create an Offscreen
		* redraw it only when its content changes
		* render to your scene each frame
		
	Use Case 3:
		* create an Offscreen
		* call offscreen.enableDirtyRects();
		* use the dirty rectangle feature to limit drawing to parts that need redrawing
		* render to your scene each frame

Implementation Notes:

The basic idea:

* create this thing (an offscreen buffer (canvas))
* do a bunch of drawing to it
*   ... only once, if that's an option!  (e.g. a static scene)
*   ... or only when things change
*   	... and only the part that changes
* and then draw it into the main canvas every frame

objective:  faster rendering most of the time, on screens where most things aren't changing.

useful for the main display, but also for things like individual UI screens or panels or parts of a display.
so, we expect several of these to exist, but also probably a rat.graphics.mainOffscreen, or something.

Features:

Dirty Rectangles
* provide an API to say when a rectangular piece of the image has changed
* track all these rectangles
* coalesce them when two overlap or one includes another, etc.
* provide an API to check against this set of rectangles, so we know if something needs to be drawn.
* do this in a separate module.  A general rectlist handling module or something, that's good at dirty rects,
	but also for whatever reason, general management of a list of rects

Rat UI integration
* given dirty rectangle info (as described above), only redraw the UI elements that changed
* ... will require the coders/designers to get serious about ui element sizes, wherever they want to use this feature effectively
* won't quite allow automatic optimization of UI screens, since we'll still need to know what is *changing*
* ... but that could be a future feature: track changes within rat.ui elements, for fully automated ui screen optimizations

Background Image Handling
* support identifying an image or set of images as "background" images
* only draw the part of the image that intersects with dirty rect (doing sub-image drawing)

Scrolling Support
* support a bigger offscreen area than eventually used, so we can have a moving camera without rebuilding the whole image every frame
* ... (until they get outside that bigger area, at which point we'll have to bump things over and draw the newly revealed part)

*/
rat.modules.add( "rat.graphics.r_offscreen",
[
	{name: "rat.graphics.r_graphics", processBefore: true },
	{name: "rat.graphics.r_color", processBefore: true },	//	for debug colors to be defined
	
	"rat.debug.r_console",
	"rat.os.r_system",
	"rat.graphics.r_rectlist",
], 
function(rat)
{
	/** 
	    Offscreen Object
	    @constructor
	   
 */
	rat.Offscreen = function (width, height)
	{
		this.canvas = document.createElement("canvas");
		this.canvas.width = this.width = width;
		this.canvas.height = this.height = height;
		this.ctx = this.canvas.getContext("2d");
		
		//	dirtyrects are not default any more.  Why use them all the time, when sometimes
		//	we really just want the easy offscreen canvas support.
		
		//	call enableDirtyRects() to use them.
		//this.dirtyRects = new rat.RectList();
		//this.dirtyRects.snapEven = true;
		
		if (rat.ui && !rat.ui.allowOffscreens)	//	double-check with rat ui global disable flag
		{
			rat.console.log("WARNING!  You're trying to use an OffscreenImage() object, but offscreens are not allowed by this host!");
		}
	};
	//rat.Offscreen.prototype.blah = true;	//	whatever

	//	STATIC vars
	rat.Offscreen.allowOffscreens = !rat.system.has.Wraith;
	
	//	enable dirty rects if they're not already set up.
	rat.Offscreen.prototype.enableDirtyRects = function ()
	{
		if (!this.dirtyRects)
		{
			this.dirtyRects = new rat.RectList();
			this.dirtyRects.snapEven = true;
		}
	};
	
	rat.Offscreen.prototype.setSize = function (w, h, force)
	{
		if (!force && this.width === w && this.height === h)
			return;	//	don't resize (which also erases) if we're already the right size.
		
		//	note: this will erase our offscreen image as well.
		this.canvas.width = this.width = w;
		this.canvas.height = this.height = h;
	};
	
	//	return context for drawing to
	rat.Offscreen.prototype.getContext = function ()
	{
		return this.ctx;
	};
	//	return canvas object.  Useful if you're doing your own drawImage call, for example.
	rat.Offscreen.prototype.getCanvas = function ()
	{
		return this.canvas;
	};
	
	//	render this canvas to another context
	rat.Offscreen.prototype.render = function (targetContext, x, y, drawWidth, drawHeight)
	{
		if (!targetContext)
			targetContext = rat.graphics.getContext();
		if (!x)
			x = 0;
		if (!y)
			y = 0;
		if (!drawWidth)
			drawWidth = this.canvas.width;
		if (!drawHeight)
			drawHeight = this.canvas.height;

		targetContext.drawImage(this.canvas, x, y, drawWidth, drawHeight);
	};
	
	//	erase any space hit by dirty rects.
	//	An alternative is to set clipping and use erase() below, but see notes there.
	rat.Offscreen.prototype.eraseDirty = function()
	{
		if (this.dirtyRects)
			this.dirtyRects.eraseList(this.ctx);
	};
	
	//	A simple clear - clear the whole offscreen image in a  low-level way,
	//	ignoring clipping, etc.
	//	Compare with erase(), which clears using clipping and can be useful in some cases.
	//	This also clears out the dirty rect list, which is invalid now.
	rat.Offscreen.prototype.clear = function (w, h, force)
	{
		//	erase offscreen
		this.canvas.width = this.canvas.width;
		
		//	and presumably you want dirty rects cleared, too.
		if (this.dirtyRects)
			this.dirtyRects.clear();
	};
	
	
	//	Erase the whole offscreen (with canvas clearRect call)
	//	compare with clear(), which empties the offscreen in a more low-level way
	//	and ignores clipping.
	//
	//	If you're using clipToDirty() below,
	//	this will only erase dirty rect space, which is often what we want.
	//
	//	IMPORTANT NOTE!
	//	On IE10, and therefore Windows 8.0,
	//	This erase call fails with a clip region more complex than a single rect.
	//	https://connect.microsoft.com/IE/feedback/details/782736/canvas-clearrect-fails-with-non-rectangular-yet-simple-clipping-paths
	//	So, don't use this in windows 8 or IE 10 with a complex clip region.
	//	(which is the whole point of dirty rect lists, so it's likely you'll have trouble)
	rat.Offscreen.prototype.erase = function()
	{
		if (rat.system.has.IEVersion === 10 && !rat.Offscreen.warnedAboutIE10)
		{
			rat.console.log("!! Warning:  This clearRect() call will fail in IE10 if you're using fancy clipping.");
			rat.Offscreen.warnedAboutIE10 = true;
		}
		
		this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
	};

	//	So, to hide those problems, let's give a single function that tries to do the right thing.
	//	Note that this will start a save() call either way, so you definitely need to unclip() later.
	rat.Offscreen.prototype.clipAndEraseDirty = function ()
	{
		if (rat.system.has.IEVersion === 10) {
			this.eraseDirty();
			this.clipToDirty();
		} else {
			this.clipToDirty();
			this.erase();
		}
	};
	
	//	set offscreen clip region to the total dirty space.
	rat.Offscreen.prototype.clipToDirty = function()
	{
		this.dirtyRects.clipToList(this.ctx);
	};
	rat.Offscreen.prototype.unclip = function()
	{
		this.dirtyRects.unclip(this.ctx);
	};
	
	rat.Offscreen.prototype.dirtyToPath = function(ctx, list)
	{
		if (!ctx)
			ctx = this.ctx;
		if (!list)
			list = this.dirtyRects.list;
		this.dirtyRects.listToPath(ctx, list);
	};
	
	//	for debugging purposes, draw an overlay effect to clearly indicate what is an offscreen buffer.
	//	pick cycling color/effect, so we can see when it changes.
	//	This is normally done right after the offscreen is rendered or updated.
	//
	rat.Offscreen.debugColors = [rat.graphics.orange, rat.graphics.darkGreen, rat.graphics.cyan, rat.graphics.blue, rat.graphics.violet];
	rat.Offscreen.prototype.applyDebugOverlay = function()
	{
		var ctx;
		//	make debug pattern if we don't have it already
		if (!rat.Offscreen.checkerImages)
		{
			rat.Offscreen.checkerImages = [];
			var colors = rat.Offscreen.debugColors;
			for (var i = 0; i < colors.length; i++)
			{
				var color = colors[i];
				var square = 16;
			
				rat.Offscreen.checkerImages[i] = document.createElement("canvas");
				rat.Offscreen.checkerImages[i].width = 2 * square;
				rat.Offscreen.checkerImages[i].height = 2 * square;
				ctx = rat.Offscreen.checkerImages[i].getContext("2d");
				ctx.fillStyle = "#FFFFFF";
				ctx.fillRect(0, 0, 2 * square, 2 * square);
				ctx.fillStyle = color.toString();//"#000000";
				ctx.fillRect(0, 0, square, square);
				ctx.fillRect(square, square, square, square);
			}
		}
		
		if (!this.debugOffscreenCycle)
			this.debugOffscreenCycle = 0;
		var cycleLength = rat.Offscreen.debugColors.length;
		ctx = this.ctx;
		//if (!this.pat)
			this.pat = ctx.createPattern(rat.Offscreen.checkerImages[this.debugOffscreenCycle], "repeat");
		
		var oldAlpha = ctx.globalAlpha;
		ctx.globalAlpha = 0.3;
		
		//ctx.fillStyle = color.toString();
		
		ctx.fillStyle = this.pat;
		
		ctx.fillRect(0, 0, this.width, this.height);
		
		this.debugOffscreenCycle = (this.debugOffscreenCycle+1) % cycleLength;
		ctx.globalAlpha = oldAlpha;
	};
		
	
} );

//--------------------------------------------------------------------------------------------------
//
//	Rat support for filtering images at the pixel level.
//	In particular, support for loading a raw image, filtering it, and creating a new image with the result,
//	with that new image accessible through a new image id, useable everywhere we support images.
//
/*

USAGE:

	See rtest, graphics_test.js
	A basic version:
			var rawImage = new Image();
			rawImage.src = "res/images/white_smiley.png";
			var filtered = rat.graphics.imageFilter.apply(rawImage, 'colorize', {color:new rat.graphics.Color(255, 120, 40)});
			
	All images passed in to this system must already be loaded!
		E.g. use rat.graphics.preLoadImages()
		and wait for rat.graphics.isCacheLoaded()
		and then use these image filters.
		
		or use maybe use
			rat.graphics.imageFilter.queueFilter('white_smiley', 'pixelscale', {scale:4}, 'white_smiley');	//	replace with scaled
			var done = rat.graphics.imageFilter.processQueue();
	
	Using your own filter function:
			var myFunc = function(imageData, filterOptions) { ... };
			var rawImage = new Image();
			rawImage.src = "res/images/white_smiley.png";
			var filtered = rat.graphics.imageFilter.apply(rawImage, myFunc, {myArg:50});
	
NOTES:
	
	Performance: 
		These functions can be slow.  In particular, getImageData and putImageData themselves are always expensive.
		For instance, even just colorizing an image over time every frame with a slowly changing color value will be expensive.
		It's generally expected that you'll set up a modified image ONCE (e.g. at boot) and not per frame.

	CORS and image tainting:
		We assume the image sources used aren't cross-origin images, and aren't tainted.  If they are, getImageData will fail.

TODO:
		* support spritesheet source files, and then making a new spritesheet of the resulting image
		* support reading from a canvas, e.g. the current screen we've rendered.
			Does this already work?  I think it might...
			
		* add a bunch of standard useful functions like
			* "auto-levels" like photoshop (I've done this before - shouldn't be too hard)
			* levels (more controlled version)
			* adjust luminance (I have code for this, too)
			* chroma-key : replace key color value (including range replace or palette mapping, and replacing with transparency)
			* scale up with nearest neighbor algorithm (blocky pixel scale)
			
		* support doing this nicely at load time,
			with a function right after rat.graphics.preLoadImages,
			which takes a data list of images and filters and destination names,
			and automatically waits for preloading of each specified image,
			and as soon as it's done, runs the filter on it.
			maybe hook straight into image loading system, or adding a loaded callback for each image.
			Also automatically make sure isCacheLoaded returns false if this work is not yet done,
			so an app can just throw these filtered versions in the list, and when cacheloading is done, move ahead as if they're all loaded.
*/

//------------------------------------------------------------------------------------
rat.modules.add("rat.graphics.r_image_filter",
[
	{ name: "rat.graphics.r_image", processBefore: true },
	"rat.debug.r_console",
	"rat.os.r_system",
],
function (rat)
{
	//	the image filter system is a set of utilities in a namespace.
	
	rat.graphics.imageFilter = {
		filters : {},	//	standard filter functions, by name.
		queue : [],	//	queued up filter jobs
	};
	rat.graphics.image_filter = rat.graphics.imageFilter;	//	old inconsistent name
	var imageFilter = rat.graphics.imageFilter;	//	for convenience below
	
	//	standard invert filter, acting on raw pixel data
	imageFilter.filters['invert'] = function(imageData, filterOptions, canvas)
	{
		var data = imageData.data;
		for (var i = 0; i < imageData.data.length; i+=4)
		{
			data[i] = 255 - data[i];
			data[i+1] = 255 - data[i+1];
			data[i+2] = 255 - data[i+2];
			//leave alpha... data[i+3]
		}
		return imageData;
	};
	
	//	standard colorize filter, using filterOption "color" parameter.
	imageFilter.filters['colorize'] = function(imageData, filterOptions, canvas)
	{
		var data = imageData.data;
		var r = filterOptions.color.r;
		var g = filterOptions.color.g;
		var b = filterOptions.color.b;
		for (var i = 0; i < imageData.data.length; i+=4)
		{
			data[i] = (data[i] * r / 255)|0;
			data[i+1] = (data[i+1] * g / 255)|0;
			data[i+2] = (data[i+2] * b / 255)|0;
			//	leave alpha
		}
		return imageData;
	};
	
	//	very simple brighten/darken filter, using filterOption "brightness" parameter (1.0 = leave it)
	imageFilter.filters['brightness'] = function(imageData, filterOptions, canvas)
	{
		var data = imageData.data;
		var f = filterOptions.brightness || 1.5;
		for (var i = 0; i < imageData.data.length; i+=4)
		{
			//	this is the oversimplified part.  Should calculate luminosity or something. :)
			//	I have that code lying around, I'm just being lazy.
			//	We could always make that another filter in the list.
			data[i] = ((data[i] * f)|0 & 0xFF);
			data[i+1] = ((data[i+1] * f)|0 & 0xFF);
			data[i+2] = ((data[i+2] * f)|0 & 0xFF);
			//	leave alpha
		}
		return imageData;
	};
	
	//	fast scale up of a pixel image.  Straight duplication of pixels, no interpolation.
	imageFilter.filters['pixelscale'] = function(imageData, filterOptions, canvas)
	{
		var data = imageData.data;
		var scale = filterOptions.scale;
		var origWidth = imageData.width;
		var origHeight = imageData.width;
		var newWidth = scale * imageData.width;
		var newHeight = scale * imageData.height;
		
		var ctx = canvas.getContext('2d');
		var destDataImage = ctx.createImageData(newWidth, newHeight);
		var destData = destDataImage.data;
		
		for (var y = newHeight-1; y >= 0; y--)
		{
			//	todo: faster version will do 4 rows at a time!
			for (var x = newWidth-1; x >= 0; x--)
			{
				var destPixel = 4 * (newWidth * y + x);
				var srcX = ((x / scale)|0);
				var srcY = ((y / scale)|0);
				var sourcePixel = 4 * (origWidth * srcY + srcX);
				destData[destPixel] = data[sourcePixel];
				destData[destPixel+1] = data[sourcePixel+1];
				destData[destPixel+2] = data[sourcePixel+2];
				destData[destPixel+3] = data[sourcePixel+3];
			}
		}
		return destDataImage;
	};
	
	/** 
	   	Apply a filter to this image
	   	return a new canvas with the filtered image.
	  	the source image argument can be one of several types: rat image, Image() object, canvas, and we'll add other type support in the future.
	   	See usage notes above.
	   
 */
	imageFilter.apply = function (srcImage, filter, filterOptions, registerName)
	{
		//	for convenience, support image names, which we look up in the image cache.
		if (typeof(srcImage) === 'string')
			srcImage = rat.graphics.findInCache(srcImage);
		
		//	if the filter is a string, it must be a reference to one of our standard filters.
		//	look it up.
		if (typeof(filter) === 'string')
		{
			var filterName = filter;
			filter = imageFilter.filters[filterName];
			if (!filter)
			{
				rat.console.log("ERROR: No such filter : " + filterName);
				return null;
			}
		}
		
		//	todo: support several types of arguments here, including canvas and imageref.
		//	todo: support multi-frame rat images here, somehow?
		//	todo: support getting a sub-portion of the image?
		
		//	if it's a rat image, get html5 image
		if (srcImage.isImage)			
			srcImage = srcImage.getImageFrame(0);
		
		//	draw to canvas so we can access pixels.
		//	we'll use this same canvas to return modified results as well.
		var canvas = document.createElement('canvas');
		canvas.width = srcImage.width;
		canvas.height = srcImage.height;
		var ctx = canvas.getContext('2d');
		ctx.drawImage(srcImage, 0, 0);
		
		//	get raw image data
		var data = ctx.getImageData(0, 0, canvas.width, canvas.height);
		
		//	now, some filters change our image size.
		//	Do that now, after we've extracted pixels but before we write new ones back.
		if (filterOptions && filterOptions.scale)
		{
			canvas.width = ((canvas.width * filterOptions.scale)|0);
			canvas.height = ((canvas.height * filterOptions.scale)|0);
			if (canvas.width > 1000)
			{
				console.log("WTF");
			}
		}
		
		//	convert
		var results = filter(data, filterOptions, canvas);
		//	write modified raw image data back to the same canvas
		ctx.putImageData(results, 0, 0);
		
		if (registerName)
			rat.graphics.addToCache(canvas, registerName);
		
		return canvas;
	};
	
	//	queue up one or more filters to be processed one at a time in calls to processQueue()
	//	like applyFilter, registerName is a way to put the resulting image in the image cache,
	//		including replacing the original image if the registerName matches the source name.
	//	"avoidRepeats" means don't process the same image again in the same way in this queue as it stands.
	//		the default is to avoid repeats for replace-in-cache images (see below),
	//		but this can be overridden if for some reason you want to run the same pass twice on an image.
	//		This "avoidrepeats" functionality only searches the existing queue of filters.
	//		if you process the queue and then add more, we have no way to avoid repeats from a previous queue.
	imageFilter.queueFilter = function (srcImage, filter, filterOptions, registerName, avoidRepeats)
	{
		//	if they passed in a list, break it down - call this function again for each, for easier handling.
		if (Array.isArray(srcImage))
		{
			for (var i = 0; i < srcImage.length; i++)
			{
				var useRegisterName = registerName;
				if (registerName && Array.isArray(registerName))
					useRegisterName = registerName[i];
				
				imageFilter.queueFilter(srcImage[i], filter, filterOptions, useRegisterName, avoidRepeats);
			}
		} else {
			
			if (registerName === srcImage && avoidRepeats === void 0)	//	if replacing in cache, default avoidRepeats to true.
				avoidRepeats = true;
			
			if (avoidRepeats)
			{
				for (var i = 0; i < imageFilter.queue.length; i++)
				{
					var entry = imageFilter.queue[i];
					if (entry.srcImage === srcImage && entry.registerName === registerName && filter === entry.filter)
					{
						//rat.console.log("avoiding repeat filter " + filter);
						return;
					}
				}
			}
			
			var newEntry = {srcImage:srcImage, filter:filter, filterOptions:filterOptions, registerName:registerName};
			imageFilter.queue.push(newEntry);
		}
	};
	
	imageFilter.processQueue = function()
	{
		if (imageFilter.queue.length > 0)
		{
			var e = imageFilter.queue.shift();
			imageFilter.apply(e.srcImage, e.filter, e.filterOptions, e.registerName);
		}
		
		if (imageFilter.queue.length <= 0)
			return true;
		return false;
	}
	
});

//--------------------------------------------------------------------------------------------------
//
//	basic animset support (animset and animsetdef classes)
//
//	AnimSetDef:
//		Understand a list of animations (each a list of frames),
//		and some high level info about how to choose between those animations...
//		including (optional) standard direction handling
//
//	AnimSet
//		a runtime object tracking current imagerefs using those animations.
//
//	This is an attempt at handling a bunch of standard animation/direction/anim-state for a single
//		object.
/*
	TODO:
		various things noted below
		
		animation substates.  Maybe track the idea of a state here?
		is it an index for sublists?
		organize all this by STATE first and DIRECTION second?
			because on some states we might limit the animation directions we bother with?
			e.g. idle only has one direction,
			or jump-slice only has left/right?
		or vice-versa?
			


*/

rat.modules.add( "rat.graphics.r_animset",
[
	{name:"rat.graphics.r_graphics",processBefore:true},
	"rat.graphics.r_image",
	
	//"rat.debug.r_console",
	//"rat.os.r_system",
], 
function(rat)
{
	//	animSetDef
	//		default update type (random, loop, etc.)
	//		animation
	//			rate
	//		direction list, with each one being a LIST of anims, e.g. walking left, jumping left
	
	/** 
	    PigRender Object
	    @constructor PigRender
	    @param {Object=} config optional great big "config" object controls how this render behaves.
	   
 */
	var AnimSet = function (animSetDef)
	{
		this.anims = [];	//	my master list of imagerefs
		
		//	process master list of anims, building an imageref for each
		for (var i = 0; i < animSetDef.anims.length; i++)
		{
			var animDef = animSetDef.anims[i];
			//	if there's no frame list, but we have a "buildfrom", process the buildfrom
			//	to make a list of frames.  This has the side effect of creating the frame list,
			//	so that future objects created with this same animSetDef can just work from
			//	an explicit frame list, which is faster.  Maybe not a big deal, though.
			if (!animDef.frames && animDef.buildFrom)
			{
				animDef.frames = [];
				for (var fIndex = 0; fIndex  < animDef.buildFrom.count; fIndex++)
				{
					var numPart = "" + fIndex;
					while (numPart.length < animDef.buildFrom.digits)
						numPart = "0" + numPart;
					var oneFrameName = animDef.buildFrom.base + numPart;
					if (animDef.buildFrom.suffix)
						oneFrameName += animDef.buildFrom.suffix;

					animDef.frames.push(oneFrameName);
				}
			}
			
			var oneAnim = new rat.graphics.ImageRef(animDef.frames);
			if (animDef.center === void 0)
				animDef.center = true;	//	this one is almost always wanted.
			oneAnim.setCentered(!!animDef.center, !!animDef.center);
			oneAnim.setFlipped(!!animDef.flipX, !!animDef.flipY);
			
			if (!animDef.animSpeed)
				animDef.animSpeed = animSetDef.defaultAnimSpeed;
			oneAnim.setAnimSpeed(animDef.animSpeed);
			
			if (!animDef.animSequence)
				animDef.animSequence = animSetDef.defaultAnimSequence;
			//	'loop' is default
			if (animDef.animSequence === 'oneshot')
				oneAnim.setAnimOneShot(true);
			else if (animDef.animSequence === 'bounce')
				oneAnim.setAnimAutoReverse(true);
			else if (animDef.animSequence === 'random')
				oneAnim.setAnimRandom(true);
			
			//	remember some extra data for convenience...
			oneAnim.animDef = animDef;
			
			this.anims.push(oneAnim);
		}
		
		//	now process states, translating named states to direct references...
		this.states = {};
		this.defaultState = null;
		if (animSetDef.states)
		{
			for (var key in animSetDef.states)
			{
				//	make my own state info
				var myState = {};
				var defState = animSetDef.states[key];
				this.states[key] = myState;
				myState.defState = defState;	//	for easy access later
				myState.name = key;
				
				//	if we don't have a default state yet, let's use this one
				if (!this.defaultState)
					this.defaultState = myState;
				
				//	if there's a direct anim name specified, set up that direct reference now.
				if (defState.anim)
					myState.animRef = this.animFromName(defState.anim);
				
				//	if there's a set of directions, copy those out now.
				if (defState.directions && defState.directions.length > 0)
				{
					myState.directions = [];
					for (var dIndex = 0; dIndex < defState.directions.length; dIndex++)
					{
						var defDirSet = defState.directions[dIndex];
						
						//	do some of my own calculations here.
						//	Also, I'm being paranoid about rounding error resulting
						//	in a missing angle in rare cases, so make these widths
						//	overlap a tiny bit.
						var width = (defDirSet.maxRad - defDirSet.minRad);
						
						//	my own version of the direction range and anim reference...
						var dirSet = {
							minRad : defDirSet.minRad,
							maxRad : defDirSet.maxRad,
							
							//	my own values for later calculations
							radWidth : width * 1.00001,	//	paranoid about gaps
							centerRad : defDirSet.minRad + width/2,
							
							//	direct reference to imageref
							animRef : this.animFromName(defDirSet.anim),
						};
						myState.directions.push(dirSet);
					}
				}
			}
		}
		this.curDirIndex = 0;	//	unknown what direction we're facing yet
		
		this.deltaAngle = 0;
		
		//	set up formally...
		this.curState = null;
		this.curAnimRef = null;
		this.setAnimState(this.defaultState.name);
		
		//	remember my master data definition.
		this.animSetDef = animSetDef;
	};
	
	//	start all non-oneshot animations on a random frame
	//	(usually so they don't match up with other animating characters)
	//	TODO: support syncing several states to the same frame, though,
	//	if their animation lengths match.
	//	e.g. have "on fire jump" and "regular jump" synced up.
	AnimSet.prototype.setRandomTime = function()
	{
		for (var aIndex = 0; aIndex < this.anims.length; aIndex++)
		{
			if (this.anims[aIndex].animDef.animSequence !== 'oneshot')
				this.anims[aIndex].setRandomTime();
		}
	};
	
	//	return direct animation (imageref) reference from an animation name.
	//	return null if not found.
	AnimSet.prototype.animFromName = function(targetName)
	{
		for (var aIndex = 0; aIndex < this.anims.length; aIndex++)
		{
			if (this.anims[aIndex].animDef.name === targetName)
				return this.anims[aIndex];
		}
		return null;
	};
	
	AnimSet.prototype.update = function(dt)
	{
		for (var i = 0; i < this.anims.length; i++)
		{
			this.anims[i].update(dt);
		}
	};
	
	//	Set the current animation state (and active animation) based on a state name
	AnimSet.prototype.setAnimState = function(stateName)
	{
		if (this.states[stateName])
		{
			this.curState = this.states[stateName];
			if (this.curState.animRef) {
				this.setAnimDirectly(this.curState.animRef);
				return this.curAnimRef;
			}
			else if (this.curState.directions)
			{
				//	make the light assumption that directions match up from state to state.
				//	if they don't, that's fine - we expect the client to tell us direction next.
				var curDirIndex = this.curDirIndex;
				if (curDirIndex >= this.curState.directions.length)
					curDirIndex = 0;
				this.setAnimDirectly(this.curState.directions[curDirIndex].animRef);
				return this.curAnimRef;
			}
		}
		//else {
			//	leave it
		//}
		//	the animation gets left at whatever it was, but we'll return null
		//	so the client knows we didn't set a new one.
		return null;
	};
	
	//	get anim state name
	AnimSet.prototype.getAnimState = function()
	{
		if (this.curState)
			return this.curState.name;
		else
			return "";
	};
	
	//	Set the active animation based on our current states and a direction.
	//	if there's direction information in my data, use it!
	AnimSet.prototype.setAnimDirFromVector = function(dx, dy)
	{
		if (!this.curState)
			return;
		if (!this.curState.directions)	//	no directions in current state, so just leave current settings.
			return;
		
		var PI = Math.PI;
		var TAU = PI * 2;
		var angle = Math.atan2(-dy, dx);
		if (angle < 0)	//	translate negative angles to positive
			angle = TAU + angle;
		
		var foundDir = 0;
		
		for (var dIndex = 0; dIndex < this.curState.directions.length; dIndex++)
		{
			var dirSet = this.curState.directions[dIndex];
			
			//	here's a whole new idea...
			//	just compare with a single angle and width.  This makes some things a lot simpler.
			var deltaRad = angle - dirSet.centerRad;
			if (deltaRad > PI)	//	coming around from the other side, angle is actually less.
				deltaRad -= TAU;
			if (deltaRad > -dirSet.radWidth/2 && deltaRad < dirSet.radWidth/2)
			{
				this.curAnimRef = dirSet.animRef;
				this.curDirIndex = dIndex;	//	remember in case we switch states and keep direction later
				this.deltaAngle = deltaRad;	//	in case client wants it, here's how far off we were.
				
				//rat.console.log("angle " + angle + " - " + dirSet.centerRad + " : " + deltaRad);
				return;
			}
			
			/*	old
			//	OK, this is annoying...
			//	We need special handling if minRad < 0.
			//	This is needed (and only supported) if the range overlaps 0,
			//	e.g. -PI/4 to +PI/4 (facing right)
			var inside = false;
			if (dirSet.minRad < 0)
			{
				//	check upper half, above zero and under maxrad
				if (angle < dirSet.maxRad)
				{
					inside = true;
				//	check lower half, below zero and over minrad
				} else {
					if ((angle - TAU) > dirSet.minRad)
						inside = true;
				}
				
			//	normal case
			} else if (angle >= dirSet.minRad && angle <= dirSet.maxRad)
				inside = true;
			
			if (inside)
			{
				this.curAnimRef = dirSet.animRef;
				this.curDirIndex = dIndex;	//	remember in case we switch states and keep direction later
				return;
			}
			*/
		}
		//	not found? leave it.
	};
	
	/*
	AnimSet.prototype.setAnimIndex = function(index)
	{
		this.curAnimIndex = index;
	};
	*/
	
	AnimSet.prototype.setAnimDirectly = function(animRef)
	{
		if (animRef.animDef.animSequence === 'oneshot')
			animRef.restartAnim();
		this.curAnimRef = animRef;
	};
	
	AnimSet.prototype.getCurAnimation = function()
	{
		return this.curAnimRef;
	};
	
	//	make this class accessible
	rat.graphics.AnimSet = AnimSet;
	
} );

//--------------------------------------------------------------------------------------------------
//
//	rat module that handles preloading sounds, images, and atlases
//

//	A example of how to use the preloader
//var preloder = new rat.utils.Preloader({
//	"audio": [],	//	For the format of this see rat.audio.loadSounds
//	"atlases": [ {atlas: "path_to_image", json: "path_to_json"}, ... ],
//	"images": [],	//	For the format of this see rat.graphics.preLoadImages
//	"strings": []	//	For the format of this see rat.strings.init (first param)
//});
//preloader.startLoad();
//
rat.modules.add( "rat.utils.r_preload",
[
	{name: "rat.debug.r_console", processBefore: true},
	
	"rat.utils.r_cycleupdate",
	"rat.graphics.r_image",
	"rat.audio.r_audio",
	"rat.utils.r_utils",
	"rat.utils.r_string",
],
function(rat)
{
	//	items that we can preload
	var preloadable = {
		audio: "audio",
		atlases: "atlases",
		images: "images",
		strings: "strings"
	};

	/** @constructor */
	/**  Create the preloader
 */
	var Preloader = function( fields )
	{
		fields = fields || {};

		//	State
		this.loadRunning = false;
		this.loading = {};
		this.loaded = {};
		var key, tag;
		for( key in preloadable )
		{
			tag = preloadable[key];
			this.loading[tag] = false;
			this.loaded[tag] = false;
		}

		//	Special handling for atlases.
		this.atlasImagesLoading = false;
		this.atlasJSONLoading = false;
		
		// Events that we fired when we finish loading
		this.onDone = [];
		
		//	Data
		this.data = {};
		for (key in preloadable)
		{
			tag = preloadable[key];
			this.data[tag] = fields[tag] || [];
		}
				
		//	Data for the preloader when firing done callbacks to make sure that the list does not get screwed up.
		this.fireCBIndex = -1;

		this.cycleUpdateFunc = this.cycleUpdate.bind(this);

		// Maybe add code here to go and find any additional images images to preload
	};
	
	/** 	How does this module log
 */
	Preloader.prototype.log = rat.console.log;	//	This is here so apps can override it.
		
	/** 	Register a new function to fire when we have finished loading
 */
	Preloader.prototype.registerOnDone = function(cb, ctx, options)
	{
		options = options || {};
		options.autoRemove = !!options.autoRemove;
		
		if( cb )
		{
			this.onDone.push({ cb: cb, ctx: ctx, options: options});
		}
	};
	
	/** 	Remove a registered done function
 */
	Preloader.prototype.unregisterOnDone = function(cb, ctx)
	{
		var index, entry;
		if( typeof(cb) === "number" )
		{
			index = cb;
			if( index >= 0 && index < this.onDone.length )
			{
				entry = this.onDone[index];
				if( index < this.fireCBIndex )
					--this.fireCBIndex;
				this.onDone.splice( index, 1 );
					return true;
			}
		}
		else if( cb )
		{
			for( index = 0; index !== this.onDone.length; ++index )
			{
				entry = this.onDone[index];
				if( entry.cb === cb && entry.ctx === ctx )
				{
					if( index < this.fireCBIndex )
						--this.fireCBIndex;
					this.onDone.splice( index, 1 );
					return true;
				}
			}
		}		
		return false;
	};
	
	/** 	Fire the one done events
 */
	Preloader.prototype.fireOnDone = function()
	{
		var entry;
		for( this.fireCBIndex = 0; this.fireCBIndex !== this.onDone.length; ++this.fireCBIndex )
		{
			entry = this.onDone[this.fireCBIndex];
			entry.cb.call( entry.ctx );	//	Fire the cb
		}
		
		this.fireCBIndex = -1;
		
		for( var index = this.onDone.length-1; index >= 0; --index )
		{
			entry = this.onDone[index];
			if( entry.options.autoRemove )
				this.unregisterOnDone( index );
		}
	};
	
	/** 	Return if everything is loaded
 */
	Preloader.prototype.isLoaded = function()
	{
		for (var key in this.loaded)
			if (!this.loaded[key])
				return false;
		return true;
	};
	
	/** 	Return if anything is loading
 */
	Preloader.prototype.isLoading = function()
	{
		return this.loadRunning;
	};

	/** 	Called once per atlas JSON that gets loaded
 */
	Preloader.prototype._loadedSingleAtlasJSON = function (entry, data)
	{
		entry.jsonData = data;
	};

	/** 	Called once per atlas that has loaded both image that gets loaded
 */
	Preloader.prototype._loadedAtlasJSON = function()
	{
		this.atlasJSONLoading = false;
	};

	/** 	Call to finish the preload of this atlas.
 */
	Preloader.prototype._loadedSingleAtlas = function (entry)
	{
		//this.log("...Finished loading atlas " +entry.name);
		rat.graphics.Image.registerSpriteSheet(entry.atlas, entry.jsonData);
	};

	//	Fired when each type of preload is finished
	Preloader.prototype._preloadSetFinished = function(set)
	{
		this.log("...Finished loading ALL " + set + ".");
		this.loading[set] = false;
		this.loaded[set] = true;
	};

	/**  Start the load process if it is not already running and we are not already loaded
 */
	Preloader.prototype.startLoad = function(cb, ctx)
	{
		var index, entry;
		if( cb )
			this.registerOnDone( cb, ctx, {autoRemove:true} );

		var tag, key;
		for (key in preloadable)
		{
			tag = preloadable[key];
			this.loaded[tag] = !this.loading[tag] && this.data[tag].length <= 0;
			this.loading[tag] = this.loading[tag] || (!this.loaded[tag] && this.data[tag].length > 0);
		}
				
		//	Is everything loaded.
		if( this.isLoaded() )
		{
			this.fireOnDone();
			return;
		}

		//	Are we already loading?
		if (this.isLoading())
			return;

		this.loadRunning = true;
		this.log("Staring the preload process.  Loading :");
		for (key in preloadable)
		{
			tag = preloadable[key];
			this.log("   "+tag+":  " + this.data[tag].length);
		}

		//	Add the atlas images to the list of images to load
		var atlases = this.data[preloadable.atlases];
		if( this.loading[preloadable.atlases] )
		{
			for (index = 0; index !== atlases.length; ++index)
				this.data[preloadable.images].push(atlases[index].atlas);
		}

		//	Run through the data and start loading what we need to load
		for (key in preloadable)
		{
			tag = preloadable[key];
			if (!this.loading[tag] || this.loaded[tag])
				continue;

			switch (tag)
			{
				case preloadable.audio:
					rat.audio.loadSounds(this.data[tag]);
					break;

				case preloadable.atlases:
					this.atlasJSONLoading = true;
					this.atlasImagesLoading = true;
					var resources = [];
					for (index = 0; index !== atlases.length; ++index)
					{
						entry = atlases[index];
						resources.push({
							source: entry.json, callback: this._loadedSingleAtlasJSON.bind(this, entry)
						});
					}
					rat.utils.loadResources(resources, this._loadedAtlasJSON.bind(this));

					//	If we are not also loading images, then start that load here
					if (!this.loading[preloadable.images])
						rat.graphics.preLoadImages(this.data[preloadable.images]);
					break;

				case preloadable.images:
					rat.graphics.preLoadImages(this.data[tag]);
					break;

				case preloadable.strings:
					//rat.console.log("init rat.strings with:" + JSON.stringify(this.data[tag]))

					rat.strings.init(this.data[tag]);
					break;

				default:
					this.log("ERROR!  Unrecognized load type!");
					break;
			}
		}

		//	Register ourself as a cycle updater
		rat.cycleUpdate.addUpdater(this.cycleUpdateFunc);
	};
	
	/**  Update once per frame
 */
	Preloader.prototype.cycleUpdate = function ()
	{
		//	Check on the status of anything that is still loading
		var key, tag;
		for (key in preloadable)
		{
			tag = preloadable[key];
			if (!this.loading[tag] || this.loaded[tag])
				continue;

			switch (tag)
			{
				case preloadable.audio:
					if (rat.audio.isCacheLoaded())
						this._preloadSetFinished(tag);
					break;

				case preloadable.atlases:
					if (this.atlasImagesLoading && rat.graphics.isCacheLoaded())
						this.atlasImagesLoading = false;
					if (!this.atlasImagesLoading && !this.atlasJSONLoading)
					{
						//	Finish the load for each atlas
						var atlases = this.data[tag];
						for (var index = 0; index !== atlases.length; ++index)
							this._loadedSingleAtlas(atlases[index]);

						this._preloadSetFinished(tag);
					}
					break;

				case preloadable.images:
					if (rat.graphics.isCacheLoaded())
						this._preloadSetFinished(tag);
					break;

				case preloadable.strings:
					if (rat.string.isReady())
						this._preloadSetFinished(tag);
					break;

				default:
					this.log("ERROR!  Unrecognized load type!");
					break;
			}
		}

		if( this.loadRunning && this.isLoaded() )
		{
			this.log("...Preload Finished.");
			rat.cycleUpdate.removeUpdater(this.cycleUpdateFunc);
			this.loadRunning = false;
			this.fireOnDone();
		}
	};

	rat.utils.Preloader = Preloader;
} );
//--------------------------------------------------------------------------------------------------
//
//	Test graphics elements (UI)
//
//	TODO: move to rtest, remove from rat engine.
//
rat.modules.add( "rat.test.r_test_ui",
[
	{ name: "rat.test.r_test", processBefore: true },
	
	"rat.debug.r_console",
	"rat.graphics.r_graphics",
	"rat.ui.r_screen",
	"rat.ui.r_ui",
	"rat.ui.r_ui_textbox",
	"rat.ui.r_ui_animator",
	"rat.ui.r_ui_shape",
	"rat.ui.r_ui_button",
	"rat.ui.r_ui_sprite",
	"rat.ui.r_ui_scrollview",
	"rat.ui.r_screenmanager",
],
function(rat)
{
	//	UI tests
	rat.test.setupUITest = function ()
	{
		//	empty element as group
		//	empty element just empty?  debug frame?
		//	sprite (animated)
		//	text box, especially labeling various text things
		//	buttons of various kinds
		//	moving UI screens on top of other UI screens
		//	tooltips

		//	screens, screen registering
		//	screen stack, moving screens

		var screenWidth = rat.graphics.SCREEN_WIDTH;
		var screenHeight = rat.graphics.SCREEN_HEIGHT;

		//	screens are just UI elements.  Make a container to hold all UI.
		var container = new rat.ui.Screen();
		container.setPos(0, 0);
		container.setSize(screenWidth, screenHeight);

		rat.screenManager.setUIRoot(container);

		var mtbox = new rat.ui.TextBox("Test: Main UI");
		mtbox.setFont("arial bold");
		mtbox.setFontSize(20);
		mtbox.setPos(0, 12);
		mtbox.setSize(screenWidth, 30);
		mtbox.setAlign(rat.ui.TextBox.alignCenter);
		mtbox.setFrame(1, rat.graphics.yellow);
		mtbox.setColor(new rat.graphics.Color(180, 180, 210));
		container.appendSubElement(mtbox);
		
		//	tap into keydown handling for test keys
		container.pixelMode = false;
		container.handleKeyDown = function(ratEvent)
		{
			//	test pixel mode impact on ui
			if (ratEvent.which === rat.keys.p)
			{
				container.pixelMode = !container.pixelMode;
				rat.graphics.setImageSmoothing(!container.pixelMode);
				return true;	//	handled
			}
			return false;
		}

		//	make a sub screen to push on the stack over that.
		//	(this helps test the screen stack)
		var EDGE = 40;
		var s = new rat.ui.Screen();
		s.setPos(EDGE, EDGE);
		s.setSize(screenWidth - 2 * EDGE, screenHeight - 2 * EDGE);

		s.setBackground(new rat.graphics.Color(100, 50, 50, 0.8));
		s.setBackgroundFrame(1, new rat.graphics.Color(250, 250, 250, 1.0));

		rat.screenManager.pushScreen(s);

		//	subscreen title text
		var title = new rat.ui.TextBox("Test: Subscreen");
		title.setFont("arial");
		title.setFontSize(18);
		title.setPos(30, 4);
		title.setSize(200, 20);
		title.setAlign(rat.ui.TextBox.alignLeft);
		title.setColor(new rat.graphics.Color(180, 180, 210));
		//title.setUseOffscreen(true);
		s.appendSubElement(title);

		//	a box for testing tooltips
		var tipBox1 = new rat.ui.TextBox("(tip)");
		tipBox1.setFont("arial");
		tipBox1.setFontSize(18);
		tipBox1.setPos(200, 80);
		tipBox1.setSize(60, 60);
		tipBox1.setColor(new rat.graphics.Color(180, 180, 210));
		tipBox1.setFrame(1, rat.graphics.white);
		s.appendSubElement(tipBox1);

		tipBox1.setCallback(function (element, userData)
		{
			rat.console.log("tip click");
			//	let's cycle through known tooltip placements...
			var placements = ['none', 'topLeft', 'top', 'topRight', 'bottomLeft', 'bottom', 'bottomRight', 'rightHigh'];
			//	userData is my desired position index, starting at -1 (below)
			userData = (userData + 1) % placements.length;	//	advance to next in list, looping if needed
			tipBox1.positionToolTip(placements[userData], { x: 0, y: 0 }, true);	//	reposition, and set mouse pos flag
			tipBox1.setCallbackInfo(userData);	//	update modified userData
		}, -1);

		//	this will by default create a simple 15-point calibri line of text in a framed box.
		var tipInfo = tipBox1.addTextToolTip("this is a\nmulti-line\ntooltip test", rat.graphics.gray, rat.graphics.black, s);

		//	a test of changing that text style...
		tipInfo.textBox.setFontStyle('italic');	//	Big changes won't work well, but italic is a simple change

		//	a test of repositioning that tooltip
		tipBox1.setToolTip(tipInfo.container, s, 'topRight', { x: -5, y: -5 }, false);

		//	a test of a moving element with tooltips
		/*
		var animator = new rat.ui.Animator(rat.ui.Animator.mover, tipBox1);
		animator.setTimer(10);	//	10 seconds slow move
		var pos = tipBox1.getPos().copy();
		var startVal = pos;
		var endVal = {x:pos.x + 300, y:pos.y + 20};
		animator.setStartEndVectors(startVal, endVal);
		animator.setAutoDie();	//	kill animator when it's done
		*/

		//	another box for testing tooltips
		var tbox = new rat.ui.TextBox("(tip 2)");
		tbox.setFont("arial");
		tbox.setFontSize(18);
		tbox.setPos(250, 120);
		tbox.setSize(60, 60);
		tbox.setColor(new rat.graphics.Color(180, 180, 210));
		tbox.setFrame(1, rat.graphics.white);
		s.appendSubElement(tbox);

		//	test custom tooltips...
		var cont = new rat.ui.Shape(rat.ui.circleShape);
		cont.setSize(40, 40);
		cont.setColor(new rat.graphics.Color(250, 250, 90));
		cont.setFrame(1, new rat.graphics.Color(250, 250, 250, 1.0));
		tbox.setToolTip(cont, s, 'bottom');

		//	manual wrap text
		tbox = new rat.ui.TextBox("...");
		tbox.setFont("arial");
		tbox.setFontSize(14);
		tbox.setPos(10, 40);
		tbox.setSize(200, 100);
		tbox.setAlign(rat.ui.TextBox.alignRight);
		tbox.setBaseline(rat.ui.TextBox.baselineMiddle);
		tbox.setColor(new rat.graphics.Color(280, 280, 210));
		tbox.setFrame(1, rat.graphics.white);
		//tbox.setUseOffscreen(true);
		s.appendSubElement(tbox);

		//	auto wrap text
		var autotextbox = new rat.ui.TextBox("...");
		autotextbox.setAutoWrap(true);
		autotextbox.setFont("calibri");
		autotextbox.setFontStyle("italic");
		autotextbox.setFontSize(17);
		autotextbox.setPos(10, 150);
		autotextbox.setSize(200, 110);
		//autotextbox.setAlign(rat.ui.TextBox.alignCenter);
		//autotextbox.setBaseline(rat.ui.TextBox.baselineMiddle);
		autotextbox.setAlign(rat.ui.TextBox.alignLeft);
		autotextbox.setBaseline(rat.ui.TextBox.baselineTop);
		autotextbox.setColor(new rat.graphics.Color(280, 280, 210));
		autotextbox.setFrame(1, rat.graphics.white);
		//autotextbox.setUseOffscreen(true);
		s.appendSubElement(autotextbox);
		
		//	auto wrap text in a small box ("some text which will squish[..]")
		var shortbox = new rat.ui.TextBox("...");
		shortbox.setAutoWrap(true);
		shortbox.setFont("arial");
		shortbox.setFontSize(14);
		shortbox.setPos(360, 140);
		shortbox.setSize(200, 40);
		shortbox.setAlign(rat.ui.TextBox.alignRight);
		shortbox.setBaseline(rat.ui.TextBox.baselineMiddle);
		shortbox.setColor(new rat.graphics.Color(280, 280, 210));
		shortbox.setFrame(1, rat.graphics.white);
		//shortbox.setUseOffscreen(true);
		s.appendSubElement(shortbox);

		//	scrollview
		var scrollView = new rat.ui.ButtonScrollView();
		scrollView.setPos(10, 270);
		scrollView.setSize(200, 200);
		var SV_SIZE_W = 1200;
		var SV_SIZE_H = 400;
		scrollView.setContentSize(SV_SIZE_W, SV_SIZE_H);
		scrollView.setFrame(1, rat.graphics.white);
		s.appendSubElement(scrollView);

		//	put some crap inside the scrollview
		var svbox = new rat.ui.Shape(rat.ui.squareShape);
		svbox.setColor(new rat.graphics.Color(50, 150, 50));
		svbox.setPos(10, 10);
		svbox.setSize(SV_SIZE_W - 20, SV_SIZE_H - 20);
		svbox.setFrame(5, rat.graphics.green);
		scrollView.appendSubElement(svbox);

		for(var x = 20; x < SV_SIZE_W; x += 40)
		{
			var littleBox = new rat.ui.Shape(rat.ui.squareShape);
			littleBox.setPos(x, 20);
			littleBox.setSize(30, 30);
			var boxColor = new rat.graphics.Color(50 + Math.floor(Math.random() * 200), 50 + Math.floor(Math.random() * 200), 50 + Math.floor(Math.random() * 200));
			littleBox.setColor(boxColor);
			littleBox.addTextToolTip("thing " + boxColor.toString(), rat.graphics.cyan, rat.graphics.darkGray, s);
			scrollView.appendSubElement(littleBox);
		}
		
		//	button in scrollview
		var but = rat.ui.makeCheapButton(null, new rat.graphics.Color(200, 150, 150));
		but.setPos(10, 80);
		but.setSize(100, 40);
		but.setTextValue("me");
		scrollView.appendSubElement(but);
		but.addTextToolTip("test button inside scrollview");	//	leaving out args works if button was already added to parent.
		but.setCallback(function (element, userData)
		{
			rat.console.log("HIT BUTTON IN SCROLLVIEW");
		});
		
		//	button
		but = rat.ui.makeCheapButton(null, rat.graphics.gray);
		but.setPos(230, 12);
		but.setSize(100, 40);
		but.setTextValue("button\ntest");
		s.appendSubElement(but);
		but.addTextToolTip("Funky button color testing", rat.graphics.cyan, rat.graphics.darkGray, s);
		but.setCallback(function (element, userData)
		{
			rat.console.log("HIT");
		});
		//	set up a bunch of custom colors!
		but.setTextColors("#FFFF80", "rgb(255,255,255)", new rat.graphics.Color(255, 255, 255, 0.5), void 0);
		var RE = rat.ui.Element;	//	for readability
		var statePairs = [
			{state: RE.enabledFlag, color:"#A08080", frameColor:"#F0A0A0"},
			{state: RE.enabledFlag | RE.highlightedFlag, color:"#A0A080", frameColor:"#FFFF80"},
			{state: RE.enabledFlag | RE.pressedFlag, color:"#A04040", frameColor:"#802080"},
			{state: RE.enabledFlag | RE.toggledFlag, color:"#8080A0", frameColor:"#A0A0F0"},	//	toggled normal
			{state: RE.enabledFlag | RE.toggledFlag | RE.highlightedFlag, color:"#80A0F0", frameColor:"#A0F0F0"},
			{state: RE.enabledFlag | RE.toggledFlag | RE.pressedFlag, color:"#6040A0", frameColor:"#6020F0"},
			//{state: 0, color:"#404040", frameColor:"#a0a0a0"},	//	disabled
		];
		but.setStateColors(statePairs);
		but.setStateFrameColors(statePairs);
		but.setToggles(true);
		//but.setEnabled(false);

		//	button 2
		but = rat.ui.makeCheapButton(null, new rat.graphics.Color(90, 180, 90));
		but.setPos(350, 12);
		but.setSize(100, 40);
		but.setTextValue("Close");
		s.appendSubElement(but);
		but.addTextToolTip("simple tooltip 2", rat.graphics.cyan, rat.graphics.darkGray, s);
		but.setCallback(function (element, userData)
		{
			//rat.screenManager.setUIRoot(null);
			rat.screenManager.popScreen();
		});

		//	sprite button
		but = rat.ui.makeSpriteButton("res/images/love.png", "res/images/love2.png", "res/images/love3.png");
		but.setPos(280, 70);
		but.setSize(16, 16);	//	this would not be needed if the image were preloaded...
		but.setScale(2, 2);	//	test
		s.appendSubElement(but);
		but.addTextToolTip("2x scaled sprite button", rat.graphics.cyan, rat.graphics.darkGray, s);
		but.setFrame(1, rat.graphics.red);
		
		//	sprite button with a big bounds, and we want image to fill it...
		but = rat.ui.makeSpriteButton("res/images/love.png", "res/images/love2.png", "res/images/love3.png");
		but.setPos(340, 70);
		but.setFlag(rat.ui.Element.autoScaleAfterLoadFlag);
		but.setSize(64, 64);
		s.appendSubElement(but);
		but.addTextToolTip("sprite button that should scale up", rat.graphics.cyan, rat.graphics.darkGray, s);
		but.setFrame(1, rat.graphics.red);

		//	bubble button
		but = new rat.ui.Button(rat.ui.Button.bubbleType, "res/images/bubblebox_normal.png", "res/images/bubblebox_high.png", "res/images/bubblebox_low.png");
		but.setPos(240, 200);
		but.setSize(69 * 2.7, 69);
		//but.setScale(2, 2);	//	test
		s.appendSubElement(but);
		but.addTextToolTip("bubble button", rat.graphics.cyan, rat.graphics.darkGray, s);
		but.setTextValue("Flip Global Scale");
		but.setCallback(function (element, userData)
		{
			if(rat.graphics.hasGlobalScale())
			{
				rat.graphics.clearGlobalScale();
			} else
			{
				rat.graphics.setGlobalScale(1.5, 1.5);
			}
		});

		//	simple sprite, animated
		var sp = new rat.ui.Sprite(["res/images/love.png", "res/images/love2.png", "res/images/love3.png"]);
		sp.setPos(600, 130);
		sp.imageRef.animSpeed = 10;
		s.appendSubElement(sp);
		sp.autoCenter();
		//console.log(sp.center.x);
		//console.log(sp.center.y);
		//sp.setClip(true);	//	broken
		
		//	scaled sprite
		var sp = new rat.ui.Sprite(["res/images/love.png", "res/images/love2.png", "res/images/love3.png"]);
		sp.setFlag(rat.ui.Element.autoScaleAfterLoadFlag);
		sp.setSize(32,32);
		sp.setPos(650, 130);
		sp.imageRef.animSpeed = 10;
		s.appendSubElement(sp);
		sp.autoCenter();
		
		//	animate test button
		but = new rat.ui.Button(rat.ui.Button.bubbleType, "res/images/bubblebox_normal.png", "res/images/bubblebox_high.png", "res/images/bubblebox_low.png");
		but.setPos(260, 300);	//	see below - we're using autoCenter
		but.setSize(140, 50);
		but.setScale(1.25, 1.25);	//	test scale
		//but.autoCenter();	//	test autocentering bubble boxes - broken
		s.appendSubElement(but);
		but.addTextToolTip("bubble button", rat.graphics.cyan, rat.graphics.darkGray, s);
		but.setTextValue("Animate");
		but.setCallback(function (element, userData)
		{
			var animator = new rat.ui.Animator(rat.ui.Animator.rotator, s);
			animator.setTimer(1);
			animator.setStartEnd(0, Math.PI / 8);
			animator.setAutoDie();	//	kill animator when it's done

			animator = new rat.ui.Animator(rat.ui.Animator.rotator, s);
			animator.setDelay(1);
			animator.setTimer(1);
			animator.setStartEnd(Math.PI / 8, 0);
			animator.setAutoDie();	//	kill animator when it's done
		});

		//	animate test button 2 - animate the button itself, and test button text colors
		but = new rat.ui.Button(rat.ui.Button.bubbleType, "res/images/bubblebox_normal.png", "res/images/bubblebox_high.png", "res/images/bubblebox_low.png");
		//var but = rat.ui.makeSpriteButton("res/images/love.png", "res/images/love2.png", "res/images/love3.png");
		//var but = rat.ui.makeCheapButton(null, new rat.graphics.Color(90, 180, 90));
		but.setPos(440, 200);
		but.setSize(140, 50);
		but.setTextColors(rat.graphics.gray, rat.graphics.yellow, rat.graphics.red);
		s.appendSubElement(but);
		but.addTextToolTip("click to animate me", rat.graphics.cyan, rat.graphics.darkGray, s);
		but.setTextValue("Animate Me");
		but.setCallback(function (element, userData)
		{
			var animator = new rat.ui.Animator(rat.ui.Animator.scaler, element);
			animator.setTimer(1);
			animator.setStartEnd(1, 2);
			animator.setAutoDie();	//	kill animator when it's done

			animator = new rat.ui.Animator(rat.ui.Animator.scaler, element);
			animator.setDelay(1);
			animator.setTimer(1);
			animator.setStartEnd(2, 1);
			animator.setAutoDie();	//	kill animator when it's done

			//	hey, let's also animate the sprite test we did...
			animator = new rat.ui.Animator(rat.ui.Animator.rotator, sp);
			animator.setTimer(2);
			animator.setStartEnd(0, 4 * Math.PI);
			animator.setAutoDie();	//	kill animator when it's done
		});
		//	test custom image function, but just set the same ones...
		//	Oh, hey, let's test animated state here..
		var stateImagePairs = [
			{state: RE.enabledFlag, resource:[
				"res/images/bubblebox_normal_f1.png",
				"res/images/bubblebox_normal_f2.png",
				"res/images/bubblebox_normal_f3.png"]},
			//	test passing in an existing imageref, as an alternative
			{state: RE.enabledFlag | RE.highlightedFlag, imageRef:rat.graphics.makeImage("res/images/bubblebox_high.png")},
			{state: RE.enabledFlag | RE.pressedFlag, resource:"res/images/bubblebox_low.png"},
		];
		but.setStateImages(stateImagePairs);
		var state = but.getMatchingState(RE.enabledFlag);
		if (state)
		{
			state.imageRef.setAnimSpeed(8);
			//state.imageRef.setAnimAutoReverse(true);
			state.imageRef.setAnimOneShot(true);	//	test
		}

		//	scaled container test
		var scont = new rat.ui.Screen();
		scont.setPos(450, 300);
		scont.setSize(100, 100);
		scont.setScale(1.5, 1.5);
		scont.setFrame(1, rat.graphics.yellow);
		//scont.addTextToolTip("box scaled 1.5", rat.graphics.cyan, rat.graphics.darkGray, s);
		s.appendSubElement(scont);

		mtbox = new rat.ui.TextBox("123");
		mtbox.setFont("arial bold");
		mtbox.setFontSize(20);
		mtbox.setPos(0, 80);
		mtbox.setSize(100, 20);
		mtbox.autoCenter();
		mtbox.setAlign(rat.ui.TextBox.alignCenter);
		mtbox.setFrame(1, rat.graphics.green);
		mtbox.setColor(new rat.graphics.Color(180, 180, 210));
		scont.appendSubElement(mtbox);

		but = rat.ui.makeCheapButton(null, rat.graphics.gray);
		but.setPos(20, 20);
		but.setSize(100, 20);
		but.setScale(1.2, 1.5);
		but.setTextValue("in scaled box");
		scont.appendSubElement(but);
		but.addTextToolTip("Test buttons inside scaled containers", rat.graphics.cyan, rat.graphics.darkGray, s);
		but.setCallback(function (element, userData)
		{
			rat.console.log("SSUB hit");
		});
		
		//	EditText
		//	auto center these, as a test of centering position of text boxes.
		var etX = 480 + 90;
		var etY = 20 + 12;
		var etbox = new rat.ui.EditText("Editable Text");
		etbox.setFont("Impact");
		etbox.setFontSize(20);
		etbox.setPos(etX, etY);
		etbox.setSize(180, 24);
		etbox.autoCenter();
		//etbox.setAlign(rat.ui.TextBox.alignRight);
		//etbox.setBaseline(rat.ui.TextBox.baselineMiddle);
		etbox.setColor(new rat.graphics.Color(220, 180, 180));
		s.appendSubElement(etbox);
		etbox.addTextToolTip("Editable Text", rat.graphics.cyan, rat.graphics.darkGray, s);
		//etbox.setUseOffscreen(true);
		
		//	EditText 2
		etbox = new rat.ui.EditText("");
		etbox.setFont("arial");
		etbox.setFontSize(30);
		etbox.setPos(etX, etY + 40);
		etbox.setSize(180, 34);
		etbox.autoCenter();
		etbox.setColor(new rat.graphics.Color(180, 180, 220));
		s.appendSubElement(etbox);
		etbox.addTextToolTip("Editable Text 2", rat.graphics.cyan, rat.graphics.darkGray, s);
		//	upside down text.  Pretty funny, but not very useful and not correct.
		//etbox.setRotation(Math.PI);

		//	done adding stuff!
		//	build input map for key/controller support
		s.autoBuildInputMap();

		//	let's animate this whole screen in.
		//	Do this AFTER we've set up everything, because this animation kicks in immediately,
		//	and we don't want all the positioning logic (and input map setup) to be affected by this animation.
		var animator = new rat.ui.Animator(rat.ui.Animator.mover, s);
		animator.setTimer(0.5);
		var startVal = { x: -200, y: -100 };
		var endVal = { x: EDGE, y: EDGE };
		animator.setStartEndVectors(startVal, endVal);
		animator.setAutoDie();	//	kill animator when it's done

		animator = new rat.ui.Animator(rat.ui.Animator.scaler, s);
		animator.setTimer(0.5);
		animator.setStartEndVectors({ x: 0.1, y: 0.1 }, { x: 1, y: 1 });
		animator.setAutoDie();	//	kill animator when it's done

		animator = new rat.ui.Animator(rat.ui.Animator.rotator, s);
		animator.setTimer(0.5);
		animator.setStartEnd(-Math.PI, 0);
		animator.setAutoDie();	//	kill animator when it's done


		//	set me up to update...

		rat.test.tests.push({
			update: rat.test.updateUITest,
			wrapTest1: { tbox: tbox, timer: 0, pos: 0, fullText:
				"This is a test\nof CR handling, especially\nwhen the text is changing\nover time\n..." },
			
			//wrapTest2: { tbox: autotextbox, timer: 0, pos: 0, fullText:
			//	"This is a test of autowrapping with one manual break here.\nThe rest is autowrapped by code..." },
			
			wrapTest2: { tbox: autotextbox, timer: 0, pos: 0, fullText:
				"借助煙霧彈隱身,隱去你. ?的蹤跡並在敵人間穿行。每次使用可獲得POWERUP_XP_GAIN點經驗。每局遊戲最多可使用ITEM_CAPACITY次。"},
			
			wrapTest3: { tbox: shortbox, timer: 0, pos: 0, fullText: "Some text which will squish when it's just barely too long, vertically.  But if it's way too long, it just eventually adds a line." },
		});

		//	temp debug- set global
		//gt = tbox;

		//	hide whole group..
		//s.setVisible(false);
	};

	rat.test.updateUITest = function (dt, testData)
	{
		function testText(data)
		{
			data.timer += dt;
			if(data.timer > 0.1)
			{
				data.timer = 0;
				data.pos++;

				var textValue = data.fullText.slice(0, data.pos);

				data.tbox.setTextValue(textValue);

				var len = data.fullText.length;
				if(data.pos === len)
					data.pos = 0;
			}
		}

		testText(testData.wrapTest1);
		testText(testData.wrapTest2);
		testText(testData.wrapTest3);
	};
} );
//--------------------------------------------------------------------------------------------------
//	Page Slider UI element
//
/*
	A UI element for showing a single page of a large set of content.
	Somewhat like a ScrollView, but
		* selective in what gets drawn - we only update and draw a page at a time
			because we want to be aggressive about performance if the content is large.
		* with special controls for moving a page at a time

	Usage
		var pager = new rat.ui.PageSlider(screen);
		//	position, size, etc.
		pager.setOrientation('vertical');
		pager.attachButtons(b1, b2);
		pager.setUserData(blah);	//	optional
		pager.setCreatePage(function(pageIndex, opts, userData){});
		pager.setDeletePage(function(pageIndex, opts, userData){});
		pager.setPageCount(103);
		pager.setPageIndex(7);
		
		the createpage function creates a pane, but doesn't add it to any containers - we handle that.
		
		deletePageFunc gives client a chance to clear internally cached references or whatever.
			It's optional - if you don't specify a deletePageFunc, it's OK, the page will get garbage collected.
		
	IMPLEMENTATION NOTES:
		So, I'm going to try making this a subclass of ScrollView so we get some of the default touch interaction
			like flinging.
		But we're quite different in some ways, like our support of pages, and we don't want zoom at all.
		
		Pages are explicitly created and disposed on the fly as needed.
		We do use contentoffset, which is standard in rat ui elements.
		
		We create and delete pages and manage them as regular subelements,
			so they get drawn and updated and tracked as normal subelements
			(and can have interactive items in them)
		
		We generally don't address zoom at all here, since it'd mess up our understanding of what
			a page is, and whether we're showing a whole page at a time.
			
		pageIndex is a logical current page number, and the target of any animation going on
		displayPageIndex is the current page being displayed, maybe animating toward pageIndex.

	TODO:
		* rename to pageview for clarity and consistency?
			(it's not an object that *does* sliding, per se, so "slider" is confusing compared with "dragger",
			and it's a lot like a scrollview, so pageview makes more sense...)
			
		* fling: when flinged in inherited code, trap that, figure out where our fling should land us (?!?) and
			animate in our own normal way to that page?
			alternative: let the fling do its thing, but as we're getting close to done, make sure we settle on a page?
		
		* a way of refreshing pages - externally we know their content changed, and
			we want to force them to be recreated.
		[x] some way of *updating* a page instead of creating it.  Probably as an argument to
			the create function!
			pass old page, pass "update" flag, and create function is welcome to just update it,
			if it wants.  Or don't even bother with the "update" flag, if it already exists.
		[x] track pages, delete unused pages
		
		* either vertical or horizontal control
			* maybe one day do both at once?
		[x] optional buttons for paging right/left
		* (low pri) optional buttons for jumping to start/end
		* half-page support!
		[x] animated sliding
		* support for dragging right/left, including partial drag animation
			* let go after you've dragged X amount, and it slides to next page
			* make sure this works for half-page mode.
			* grab in the middle of animating
			* (low pri) support scale-up-drag multiplier?
		* support for short fling right/left
		* mousewheel support
		* scrollbar?
		* pips indicating number of pages and current page
		

*/
	
rat.modules.add( "rat.ui.r_ui_pageslider",
[
	{name: "rat.ui.r_ui", processBefore: true },
	{name: "rat.ui.r_ui_scrollview", processBefore: true },
	{name: "rat.utils.r_utils", processBefore: true },
	
	"rat.debug.r_console",
], 
function(rat)
{
	/**
	 * @constructor
	 * @extends rat.ui.ScrollView
	*/
	rat.ui.PageSlider = function (parentView)
	{
		rat.ui.PageSlider.prototype.parentConstructor.call(this, parentView); //	default init
		this.setClip(true);	//	page sliders generally want to clip, so sliding-in/out parts are drawn correctly!

		this.orientation = 'horizontal';
		this.pageIndex = 0;
		this.displayPageIndex = 0;
		this.lastFocusedPage = -1;	//	the last page that was most centered in the view
		this.pageCount = 0;
		this.pageAnim = null;
		
		this.config = {
			animTime : 0.5,	//	seconds for single page slide
		};
		
		this.pages = [];	//	array of ordered pages
		
		//	set up inherited scrollview behavior
		this.wheelAffects = 'x';	//	default to wheel scrolling horizontally until we hear otherwise.
		this.supportZoom(false);
	};
	rat.utils.inheritClassFrom(rat.ui.PageSlider, rat.ui.ScrollView);
	
	//	default values for PageSlider objects
	rat.ui.PageSlider.prototype.elementType = "PageSlider";
	
	rat.ui.PageSlider.prototype.setUserData = function (userData)
	{
		this.userData = userData;
	};
	
	//	set how much time is taken for animating from page to page
	//	on discrete input like clicking attached prev/next buttons
	rat.ui.PageSlider.prototype.setAnimTime = function (time)
	{
		this.config.animTime = time;
	};
	
	rat.ui.PageSlider.prototype.setOrientation = function (orientation)
	{
		this.orientation = orientation;
	};
	
	//	TODO: adapt content size, current contentoffset, and page positions if my bounds change!
	
	rat.ui.PageSlider.prototype.setPageCount = function (count)
	{
		//	TODO: shortened count? delete those pages.
		var oldCount = this.pageCount;
		this.pageCount = count;
		if (this.pageIndex > count-1)	//	shortened page count below current page?
		{
			this.setPageIndex(count-1, false);
		}
		else if (oldCount === 0 || this.pageIndex === count-1)	//	newly visible page?
		{
			this.callCreatePage(this.pageIndex);	//	update it
			this.lastFocusedPage = -1;
			this.updateFocusedPage();
		}
		this.updateButtonState();
		this.contentSize.x = this.size.x * count;
		this.contentSize.y = this.size.y;
	};
	
	//	Set current page.
	//	By default, this assumes we want to animate display to that page over time,
	//	which happens through displayPageIndex.
	rat.ui.PageSlider.prototype.setPageIndex = function (index, doAnimate)
	{
		if (doAnimate === void 0)
			doAnimate = true;
		//	except if we don't have content for the current display page, don't animate.
		//	This is important during initial setup, when we're being told what page to start on,
		//	but there wasn't a previous page displayed to animate away from.
		if (!this.pages[(this.displayPageIndex|0)])
			doAnimate = false;
		
		//	clamp
		if (index >= this.pageCount)
			index = this.pageCount-1;
		if (index < 0)
			index = 0;
		
		//	wrong... this needs to be based on what's coming into view...
		//if (this.pageIndex !== index)
		//	this.callCreatePage(index);	//	make sure the newly visible page is updated
	
		this.pageIndex = index;
		//	animation happens in update code (pageIndex and displayPageIndex)
		
		this.updateButtonState();
		
		if (doAnimate)
			this.startPageAnimation();
		else
			this.snapPageAnimation();
	};
	
	//	attach (optional) standard navigation buttons.
	//	This is so the pageslider can automatically handle updating button state and reacting to clicks.
	rat.ui.PageSlider.prototype.attachButtons = function (b1, b2)
	{
		//	if they passed in an ID, let's make some big assumptions,
		//		e.g. - the buttons are in our parent, our parent has been set, etc.
		if (typeof(b1) === 'string')
			b1 = this.parent.findSubElementByID(b1);
		if (typeof(b2) === 'string')
			b2 = this.parent.findSubElementByID(b2);
		
		this.button1 = b1;
		this.button2 = b2;
		//	todo: outer functions that use userData instead of closure.
		this.button1.setCallback(function (element, userData)
		{
			var pager = userData;
			//rat.console.log("page -1");
			pager.setPageIndex(pager.pageIndex-1);
		}, this);
		this.button2.setCallback(function (element, userData)
		{
			var pager = userData;
			//rat.console.log("page +1");
			pager.setPageIndex(pager.pageIndex+1);
		}, this);
		
		this.updateButtonState();
	};
	
	//	update button visibility/enable based on current logical page index.
	//	This is the point of attached buttons - so we can automatically update their state.
	//	note that index can be floating point value.
	//	todo: support choice of disabling or hiding
	rat.ui.PageSlider.prototype.updateButtonState = function ()
	{
		if (this.button1)
		{
			if (this.pageIndex === 0)
				this.button1.setVisible(false);
			else
				this.button1.setVisible(true);
		}
		if (this.button2)
		{
			if (this.pageIndex >= this.pageCount-1)
				this.button2.setVisible(false);
			else
				this.button2.setVisible(true);	
		}
	};
	
	//	Set create page function
	rat.ui.PageSlider.prototype.setCreatePage = function (f)
	{
		this.createPageFunc = f;
		if (this.pageCount > 0)	//	if we have any pages, create current one (todo: half pages!)
			this.callCreatePage(this.pageIndex);
	};
	//	set delete page function
	rat.ui.PageSlider.prototype.setDeletePage = function (f)
	{
		this.deletePageFunc = f;
	};
	//	Set function to call when current visible page changes
	rat.ui.PageSlider.prototype.setVisiblePageChanged = function (f)
	{
		this.visiblePageChangedFunc = f;
		this.updateFocusedPage();
	};
	
	//	create a single page (by INDEX)
	//	This should be the bottle neck for all page creation.
	rat.ui.PageSlider.prototype.callCreatePage = function (pageIndex)
	{
		pageIndex = pageIndex|0;	//	should already be the case, but make sure.
		
		//	already had a page?  If so, remove it from container,
		//	but don't call delete function, since createPageFunc may reuse (update) that object.
		//	we only delete pages that are out of our view. (handled elsewhere)
		var oldPage = this.pages[pageIndex];
		if (oldPage)
			this.removeSubElement(oldPage);
		
		if (!this.createPageFunc)
			return;
		
		//rat.console.log("create page " + pageIndex);
		
		//	create this page by index.
		var opts = {
			container : this,
			size : this.size,
			oldPage : oldPage,
			//	other stuff for sure, I just can't remember what.
		};
		var page = this.createPageFunc(pageIndex, opts, this.userData);
		
		//	WE are responsible for adding that page to the container.
		//	todo: check?
		this.appendSubElement(page);
		
		//	who deletes the old page, if it wasn't reused?  Us?
		if (oldPage !== page && this.deletePageFunc)
			this.callDeletePage(oldPage);
		
		//	position the page in absolute terms, in a large space,
		//	and count on a contentoffset to keep the correct pages being actively displayed.
		var posX = pageIndex * this.size.x;
		page.setPos(posX, 0);	//	todo vertical orientation
		
		//	note: page may actually be same object, just updated.  And that's OK.
		this.pages[pageIndex] = page;
	};
	
	//
	//	delete this page (by reference!), if we have a deletePageFunc.
	//	removing the page from our pages[] table and from our subelements is handled elsewhere.
	//
	rat.ui.PageSlider.prototype.callDeletePage = function(page)
	{
		if (page)
		{
			//rat.console.log("delete page.");
			
			if (!this.deletePageFunc)
				return;
		
			var opts = {
				container : this,
				size : this.size,
			};
			
			this.deletePageFunc(page, opts, this.userData);
		}
	};
	
	//	Scroll view handle mouse wheel inside me.
	rat.ui.PageSlider.prototype.mouseWheelEvent = function (pos, ratEvent)
	{
		var handled = rat.ui.PageSlider.prototype.parentPrototype.mouseWheelEvent.call(this, pos, ratEvent);
		
		//	TODO: fix page destination and animate to it
		//	TODO: wheel sensitivity should match pages.  A single wheel input tick should go page to page,
		//	ignoring any wheelSensitivity that was set in scrollview?
		
		return handled;
	};

	//	mouse down
	//	pos is in local space
	rat.ui.PageSlider.prototype.mouseDown = function (pos, ratEvent)
	{
		var handled = rat.ui.PageSlider.prototype.parentPrototype.mouseDown.call(this, pos, ratEvent);
		return handled;
	};

	//	mouse up
	//	called whether the mouseup happened in this element or not,
	//	in case we were tracking the mouse.
	//	pos is in local space
	rat.ui.PageSlider.prototype.mouseUp = function (pos, ratEvent)
	{
		var handled = rat.ui.PageSlider.prototype.parentPrototype.mouseUp.call(this, pos, ratEvent);
		
		//	If they are flinging, let that happen.  Otherwise, animate to nearest page.
		if (this.isFlung())
		{
			
		} else {
			var curFocusedPage = (this.displayPageIndex + 0.5)|0;
			if (curFocusedPage === this.displayPageIndex)	//	already there
			{
				this.setPageIndex(curFocusedPage, false);
			} else {
				this.setPageIndex(curFocusedPage, true);
				//	OK, but we don't really want to take the full animation time.  So fix that now.
				this.quickSettleToPage();
			}
		}
		
		return handled;
	};

	//
	//	Handle mouse move, including passing to sub elements.
	//	This is a good time to track dragging.
	//	pos is in parent coordinates
	//
	rat.ui.PageSlider.prototype.handleMouseMove = function (newPos, handleLeaveOnly, ratEvent)
	{
		var handled = rat.ui.PageSlider.prototype.parentPrototype.handleMouseMove.call(this, newPos, handleLeaveOnly, ratEvent);
		
		//	tracking a drag?  if so, update display page
		if (this.flags & rat.ui.Element.trackingMouseDownFlag)
			this.updateDisplayPageFromScroll();
		
		return handled;
	};
	
	//	Start page animation - animate from our currently displayed page to a new destination page.
	rat.ui.PageSlider.prototype.startPageAnimation = function()
	{
		this.pageAnim = {
			time : 0,
			totalTime : this.config.animTime,
			source : this.displayPageIndex,
			dest : this.pageIndex,
		};
	};
	
	//	Snap animation - immediately display whatever our current page index is, instead of animating to it.
	rat.ui.PageSlider.prototype.snapPageAnimation = function()
	{
		//rat.console.log("snap");
		this.pageAnim = null;
		this.displayPageIndex = this.pageIndex;
		this.contentOffset.x = -this.displayPageIndex * this.size.x;
		
		this.viewChanged();
		
		//this.updateFocusedPage();
	};
	
	//	expecting that the target page is nearby,
	//	quickly settle to that page instead of taking our usual full animation time
	rat.ui.PageSlider.prototype.quickSettleToPage = function()
	{
		if (this.pageAnim)
		{
			var delta = this.pageAnim.dest - this.displayPageIndex;
			delta = Math.abs(delta);
			this.pageAnim.totalTime = delta * this.config.animTime;
		}
	};

	//	set display page
	//	display page can be fractional here.
	//	I used to be doing page callback management here, but I'm doing them all in viewchanged now.
	rat.ui.PageSlider.prototype.setDisplayPage = function(newPageIndex)
	{
		this.displayPageIndex = newPageIndex;
	};
	
	//	This handled is called any time contentOffset changes,
	//	at least for well-behaved code.
	//	So, it's the perfect time to figure out which pages are in view, which are out of view, and what pane is "focused".
	//	And it lets us consolidate all that in one place.
	//	Sure, we don't know much about what direction we're animating, but we can maybe add that later, in here, based on tracking old positions?
	//	A huge advantage to this approach (concentrating all page create/delete/focus logic here) is that any external scrolling efforts,
	//	e.g. using a rat ui animator, or new fling code or something, will all result in correct page handling.
	//
	//	TODO: timers or something to avoid deleting/creating the same page in quick succession, e.g. if the user is jittering mouse back and forth?
	rat.ui.PageSlider.prototype.viewChanged = function()
	{
		//	todo: detect actual change?
		
		this.updateDisplayPageFromScroll();
		
		//	visible pages are current page and next page,
		//	(if our display page has any fractional portion.)	
		var basePage = (this.displayPageIndex | 0);
		var basePage2 = basePage + 1;
		if (this.displayPageIndex === basePage)	//	no fractional part!
			basePage2 = -1;
		
		//	Did a new page just now come into view?
		//	let's just check the status of the currently visible pages.
		if (!this.pages[basePage])
			this.callCreatePage(basePage);
		if (basePage2 != -1 && !this.pages[basePage2])
			this.callCreatePage(basePage2);
	
		//	delete ANY pages that are made invisible now.
		//	They could be far away, e.g. if we snapped to page 13 and we were on page 4.
		for (var k in this.pages)
		{
			//	if this is one of our visible pages, skip it.
			if (k === "" + basePage
					|| ((basePage2 !== -1) && k === "" + basePage2))
				continue;
			
			//rat.console.log("delete page " + k);
			var page = this.pages[k];
			this.removeSubElement(page);
			this.callDeletePage(page);
			delete this.pages[k];	//	leave no reference in that slot.
		}
		
		this.updateFocusedPage();
	};
	
	//	set display page, including calling appropriate load/unload functions based on what's coming into view.
	//	this pageIndex can be a fractional value, e.g. in the middle of an animation or drag or fling.
	rat.ui.PageSlider.prototype.updateDisplayPageFromScroll = function()
	{
		var pageIndex = -this.contentOffset.x/this.size.x;
		//rat.console.log("update dpfs " + pageIndex);
		this.setDisplayPage(pageIndex, false);
	};
	
	//
	//	Update every frame.  A good time to handle animation,
	//
	rat.ui.PageSlider.prototype.updateSelf = function (dt)
	{
		//	in order to handle things like having been flung, see if 
		//	give inherited system a chance to update
		rat.ui.PageSlider.prototype.parentPrototype.updateSelf.call(this, dt);
		
		//	Are we being flung?  If so, let that happen, but at some point take over.
		//	This is going to be tricky, and probably broken sometimes?  Hmm...
		if (this.isFlung())
		{
			//	get fling velocity in terms of pages.
			var flingVel = this.flingVelocity.x/this.size.x;
			//	figure out if we're slowing down, ready to settle on a page,
			//	with a huge bias toward "the next page" in any given direction,
			//	and NOT settling on the page we were on when the fling started.
			var startPage = (-this.grabOffset.x/this.size.x + 0.5)|0;
			
			//rat.console.log("pfv " + flingVel);
			var targetPage = -1;
			
			//	hey, this is actually working fairly well.  The idea is to figure out if they're flinging hard,
			//	and if so, let it spin through several pages,
			//	and otherwise (soft fling) prefer a single page flick.
			var stopBias = 4;	//	ugh, what is this based on ?  It's a measure of how hard they're flinging, but how do we know?
			if (this.flingHard)
				stopBias = 1;	//	hard fling - don't stop easily
			
			if (flingVel > 0 && flingVel < 1 * stopBias / this.config.animTime)
			{
				//	on our way to prev page, so stop there.
				//	Hmm...  this logic (to pick the centrally displayed page)
				//	is resulting in snapping back the way we came, which is really not ideal...
				//	try without that, and make sure hard fling detection is fairly high
				targetPage = this.displayPageIndex | 0;
				//targetPage = (this.displayPageIndex + 0.5 - 0.05)|0;
				if (targetPage === startPage)
					targetPage = startPage-1;
			} else if (flingVel < 0 && flingVel > -1 * stopBias / this.config.animTime)
			{
				//targetPage = (this.displayPageIndex + 0.5 + 0.05)|0;
				//targetPage = this.displayPageIndex | 0;
				targetPage = (this.displayPageIndex + 0.5 + 0.05)|0;
				if (targetPage === startPage)
					targetPage = startPage+1;
			}
			if (targetPage > -1)
			{
				//rat.console.log("on page " + this.displayPageIndex + ", go to " + targetPage);
				//rat.console.log("  startPage was " + startPage + " with graboff " + this.grabOffset.x);
				this.setPageIndex(targetPage, true);
				this.quickSettleToPage();
				//	and stop fling!
				this.flingVelocity.x = this.flingVelocity.y = 0;
			}
		}
		
		//	now update our own animations
		if (this.pageAnim)
		{
			//	pageIndex is our logical target page.
			//	displayPageIndex approaches that over time, and we base contentoffset on that.
			
			this.pageAnim.time += dt;
			if (this.pageAnim.time >= this.pageAnim.totalTime)
			{
				this.setDisplayPage(this.pageIndex, this.pageAnim);
				this.pageAnim = null;
			} else {
				var f = rat.ui.Animator.filterEaseOut(this.pageAnim.time / this.pageAnim.totalTime);
				this.setDisplayPage(rat.utils.interpolate(this.pageAnim.source, this.pageAnim.dest, f), this.pageAnim);
			}
			
			//	update visual position
			this.contentOffset.x = -this.displayPageIndex * this.size.x;
			this.viewChanged();
			
		}
		
	};
	
	//	figure out which page is *centered* most, and see if that's different from before...
	//	If we have a "visiblePageChangedFunc", this is where that applies.
	rat.ui.PageSlider.prototype.updateFocusedPage = function (dt)
	{
		if (this.visiblePageChangedFunc)
		{
			var curFocusedPage = (this.displayPageIndex + 0.5)|0;
			if (this.lastFocusedPage !== curFocusedPage)
			{
				this.lastFocusedPage = curFocusedPage;
				var opts = {
					container : this,
					size : this.size,
				};
				this.visiblePageChangedFunc(curFocusedPage, opts, this.userData);
			}
		}
	};
	
	//--------------------------------------------------------------------------------------
	//	Setup from data
	
	rat.ui.PageSlider.editProperties = [
	{ label: "page slider",
		props: [
			
			//{propName:'wheelSensitivity', type:'float', defValue:32, tipText:"If used for zoom, use a really low value, like 0.1"},
			{propName:'orientation', type:'string', defValue:"horizontal"},
			{propName:'pageCount', type:'integer', defValue:"10"},
			{propName:'startingPage', type:'integer', defValue:"0"},
		],
	}
	];

	rat.ui.PageSlider.setupFromData = function (pane, data, parentBounds)
	{
		rat.ui.data.callParentSetupFromData(rat.ui.PageSlider, pane, data, parentBounds);
		//	todo: apply above props!
		//	note, when setting starting pageIndex, also set displayPageIndex
	};
});

//--------------------------------------------------------------------------------------------------
//
//	UI support (for game UI and for general graphics usefulness)
//
//	This module defines the rat.ui space and the rat.ui.Element class.
//
//	Subclasses are defined in their own modules, but this module is still big and complicated
//		because the ui.Element class handles a ton of ui functionality all at the base class level.
//
//	DIRTY and OFFSCREEN
//		The offscreen system is a way to optimize rendering - if an element is not changing every frame, then
//		it's faster to render changes once to an offscreen canvas,
//		and then render that canvas from then on whenever the ui element is drawn.
//		
//		So, we support setting a "useOffscreen" flag on any element.
//			myPane.setUseOffscreen(true);
//
//		We then need to keep track of when the offscreen render needs updating, so this is what the "dirty" flag is for.
//		A ton of different actions can potentially set the dirty flag for a given element.  It depends on the element.
//		For instance, highlighting a button makes it dirty.  Changing text makes a textbox dirty.
//			myPane.setDirty(true);
//
//		The actual rendering of the offscreen buffer happens right before an element normally needs to be drawn
//		(in the "draw" function below)
//		This is a good place because it means a dirty flag can get set by lots of operations,
//		but the offscreen re-render (which we assume is expensive) only happens once.
//
//		There are all kinds of subtle aspects of this process.  It's tricky.  See implementation below.
//		Because it may not always be exactly what you want, offscreen rendering is off by default.
//		It is assumed that each game will set this flag on a case-by-case and element-by-element basis,
//		and confirm that the desired results are achieved.
//
//		Offscreen support is also not 100% implemented for all element types yet.
//		It's not working yet for scrollviews, and in some cases like rotation, I suspect...?
//		It's working for containers, buttons, and text boxes, assuming these don't have custom code that fails to setDirty() properly,
//		and assuming they don't contain something that also fails to setDirty() properly.
//
//		Where it does work, it works nicely.  It's really easy to turn on for a container and get an immediate performance boost.
//
//		Debugging offscreen usage:
//		rat.ui.debugOffscreens = true
//		will turn on a really neat visual debug indication of where offscreen buffers are being used, and when they change.
//		(or use the console cheat "debugOffscreens")
//		This will only apply to newly created ui elements after the flag is set.
//

//	CENTERING:
//		Currently, the way this works is we track a separate center offset (x and y) (basically, an anchor)
//		When drawing, we include that offset.  When calculating things like bounds, we include that offset.
//		A different approach would be to only center once when an element is being set up, e.g. when autoCenter is called.
//			In that case, we'd immediately change this.place.pos, and move on, and never look at centering again.
//			Pros:
//				* Less math and fiddling with "center" value later!
//			Cons:
//				* Basically, any rotation gets WAY harder and uglier if we don't have an anchor concept.
//					e.g. animating rotation over time.  Really need this concept of centering for rotation to work nice.
//				* It's also nice to center things and not worry about how big they are later, e.g. when moving them around
//
//			Note that we already do the one-time approach in some functions like centerInParent.
//			I think the *ideal* thing would maybe be to rename most of these "center" things to "anchor".
//
//		Centering is currently broken with bubblebox buttons - doesn't call autocenter on its children.  should it?  see below.
//		Centering is currently broken with clipping
//
//		What we OUGHT to do is apply center.x and center.y in draw() below, in the translate call.
//		Why don't we?
//	
//		Also, I think local/global conversions don't handle center very well or consistently.
//
//	TODO:
//		* Fix all use of global ctx in various ui modules
//		* some functions to auto-distribute sub-elements spatially inside an element.
//
//------------------------------------------------------------------------------------
rat.modules.add( "rat.ui.r_ui",
[
	{ name: "rat.os.r_events", processBefore: true },
	{ name: "rat.graphics.r_offscreen", processBefore: true },
	{ name: "rat.debug.r_console", processBefore: true },
	
	"rat.os.r_system",
	"rat.math.r_vector",
	"rat.graphics.r_graphics",
	"rat.utils.r_shapes",
	"rat.ui.r_ui_animator",
	"rat.ui.r_ui_tooltip",
	"rat.math.r_math",
	"rat.utils.r_collision2d",
	"rat.ui.r_ui_textbox",
	"rat.ui.r_ui_edittext",
	"rat.ui.r_ui_data",
	"rat.utils.r_eventmap"
], 
function(rat)
{
    /** 	Rat ui module - a ui namespace for high-level user interface functionality,
	   	and a collection of ui classes for implementing specific features.
	   
	    @namespace
 */
	rat.ui = {};

	rat.ui.nextElementID = 1;	//	incrementing unique ID for each element we create
	rat.ui.mouseMoveCallCount = 0;	//	debug
	rat.ui.updateCallCount = 0;	//	debug
	
	//	global offscreen debug display system which is pretty awesome (see above)
	rat.ui.debugOffscreens = false;
	//	console cheat for the same
	rat.console.registerCommand( "debugOffscreens", function()
	{
		rat.ui.debugOffscreens = !rat.ui.debugOffscreens;
	}, ["debugOffscreen", "showOffscreens"] );
	
	//	global disable for rat ui system offscreen usage!  This is important for systems like Wraith,
	//	where offscreen canvas rendering is not yet supported.
	//	This is a good flag for game-specific offscreen rendering to check as well.
	//	Allowed by default, of course.
	rat.ui.allowOffscreens = true && rat.Offscreen.allowOffscreens;
	
	//------------------------------------------------------------------------------------
	//	basic ui element.

	//	constructor for ui Element
	/**
	 * "Element" is the base ui class that all other ui elements inherit from.
	 * This class has all the basic functionality like positioning, drawing,
	 *	containing other elements, etc.
	 
	 In addition, rat.ui.Element objects act as a simple container class, and are frequently used
		as such to group subelements together and let them be moved around, hidden, etc., all
		at once.
	
	* @constructor
	* @param {Object=} parent - optional parent object
	*/
	rat.ui.Element = function (parent)
	{
		//	Have we already been constructed?
		//	This can happen with diamond inheritance
		//	See r_ui_fillSprite
		if (this.id !== void 0)
			return;

		//console.log("Element cons");
		this.id = rat.ui.nextElementID++;
		//	consider:  also set a 'uniqueID' property that doesn't change, so ALL panes have some truly unique identifier, which is needed in a few cases.
		//		(use the same initial value, since nextElementID is unique)
		
		/** @todo	replace with standard position tracker object...  (pos + rot)
 */
		this.place = new rat.Position(0, 0, 0);
		this.center = new rat.Vector(0, 0); //	default to draw from upper left
		this.color = new rat.graphics.Color(); //	dang, this is useless for images, currently... TODO: set to null and only set if needed.
		this.size = new rat.Vector(0, 0);
		this.scale = new rat.Vector(1, 1); 	//	overall scale for rendering and interacting with content.
		this.opacity = 1.0;

		this.tempRect = new rat.shapes.Rect();

		this.contentOffset = new rat.Vector(0, 0);	//	for scrolling internal content around
		this.contentSize = new rat.Vector(0, 0);	//	for limiting scrolling and other stuff
		this.contentScale = new rat.Vector(1, 1);	//	scaling content, e.g. zoom
		this.contentScaleMin = new rat.Vector(0.1, 0.1);
		this.contentScaleMax = new rat.Vector(10, 10);

		this.flags = 0;
		this.flags |= rat.ui.Element.enabledFlag;	//	everything is enabled by default
		this.flags |= rat.ui.Element.visibleFlag;	//	everything is visible by default
		this.flags |= rat.ui.Element.adjustForScaleFlag;	//	normally, some element subclasses will try to fix bugs that come up when scaled
		
		//	TODO: most elements should NOT have tracksmouse flag set.
		//	If you're an interactive element, you must set this yourself.
		this.flags |= rat.ui.Element.tracksMouseFlag;	//	most elements track mouse.  we'll clear this on a case by case basis.
		
		
		this.name = "<elem>" + this.id; //	name is useful for debugging

		this.command = 0;	//	for triggering, e.g. buttons
		this.commandInfo = 0;

		this.callback = null;	//	call this when element is "hit" or triggered, or whatever.  like command, above.
		this.callbackInfo = null;
		//	see also flagsChangedCallback

		this.events = {};

		this.frameWidth = 0;
		this.frameColor = new rat.graphics.Color(0, 0, 0);
		this.frameOutset = 0;	//	push frame out a bit - helps with thick frames around content

		//	optional offscreen rendering optimization
		this.offscreen = null;
		this.useOffscreen = false;
		this.isDirty = true;	//	offscreen always dirty when first created
		
		//	update optimization - we assume everybody wants an update,
		//	but you can turn this off on a per-object basis, to disable updating for the pane and all subelements.
		//	It's generally expected that you would turn this off in your updateSelf() function, or externally.
		//	Turn off on single objects at a time.  Their parents will figure out if they can turn themselves off as well.
		//	But when you turn ON the flag, you need to call setNeedsUpdate(), so it can reenable the whole parent chain.
		//	See update() and setNeedsUpdate() below.
		//	Note that this whole system is not used in all rat games, currently - but IS used in the Xui system.
		//	So, it's easy to ignore this stuff entirely and only dig into it if you feel you need to optimize update calls
		//	for a complex game/scene.
		this.needsUpdate = true;

		//this.subElements = undefined
		//this.palette = undefined

		if (parent)
		{
			parent.appendSubElement(this);
		} else
			this.parent = null;
		
		this.toolTip = null;
		
		//	TODO:  We should probably switch this around and say
		//	NO elements track mouse by default,
		//	and then only set it for the ones that do.
		//	elements themselves don't generally track mouse.
		//	but we don't know if this constructor is being called for some subclass...
		//this.setTracksMouse(false);
		
	};
	
	//	some default rat ui element property values,
	//	which can be overridden in individual objects, but otherwise don't take up more space.
	//	TODO: more of the above should be here?
	rat.ui.Element.prototype.elementType = 'element';	//	various subclasses change this

	//	state flags for elements
	rat.ui.Element.highlightedFlag = 0x0001;	//	highlighted, e.g. being moused over
	rat.ui.Element.enabledFlag = 0x0002;		//	enabled (can be highlighted, targeted, pressed)
	rat.ui.Element.pressedFlag = 0x0004;		//	is currently being pressed
	rat.ui.Element.toggledFlag = 0x0008;		//	is toggled (for things that have a toggle)
	rat.ui.Element.visibleFlag = 0x0010;		//	is visible (draws in standard draw process)
	rat.ui.Element.clipFlag = 0x0020;			//	should we clip to our bounds when drawing?
	rat.ui.Element.targetableFlag = 0x0040;		//	targetable (in input map, for example)
	rat.ui.Element.targetedFlag = 0x0080;		//	is currently a target for input (not the same as highlighted)

	rat.ui.Element.mouseInFlag = 0x0100;	//	mouse is currently in my bounds (useful for tooltips)
	rat.ui.Element.trackingMouseDownFlag = 0x0200;	//	actively tracking mouse down

	//	tracksmouse is different from enabled.  !tracksmouse means don't even process mouse.
	//	enabled might change over time, unlike tracksmouse, and we might track disabled elements for things like tooltips?
	//	tracksmouse indicates whether this element should care at all about tracking mouse down or mouse movement.
	//	if tracksmouse is false, the element won't get clicks at all, and will never have trackingMouseDownFlag set.
	//	Only some elements (interactive ones like buttons) have tracksmouse set by default.
	rat.ui.Element.tracksMouseFlag = 0x0400;	//	track mouse at all.  If false, don't even try to track.

	rat.ui.Element.handleAllMouseInMe = 0x0800;	//	handle all mouse events in me, even if one of my children did

	rat.ui.Element.autoSizeAfterLoadFlag = 0x1000;	//	after loading content, set our size to match content
	rat.ui.Element.autoCenterAfterLoadFlag = 0x2000;	//	after loading, automatically center content (not finished)
	rat.ui.Element.autoScaleAfterLoadFlag = 0x4000;	//	after loading content, automatically scale content so content matches existing size
	
	rat.ui.Element.adjustForScaleFlag = 0x00010000;	//	some elements need to fiddle with rendering to make them look good when scaled.  See BubbleBox
	rat.ui.Element.unSmoothFlag = 0x00020000;	//	unsmooth scaling (set render mode when drawing, whatever my content is)
	
	rat.ui.Element.drawTiledFlag = 0x00100000;	//	automatically draw me tiled.  Useful for sprites, if nothing else.
	
	//	by default, no flag changes should set me dirty, because my look doesn't change when my flags change.
	//	even "visible", because that just means I don't get drawn at all!
	//	We may want to add something here, though, like the clip flag?  Not sure about that one yet.
	//	This is an inheritable property that various classes can change.  E.g. buttons display differently based on flags!
	//	Maybe we should set unSmoothFlag here?
	rat.ui.Element.prototype.flagsThatDirtyMe = 0;
	
	//	on the other hand, some flags should always dirty my parent, like "visible".
	//	This may actually be the only one?  note that flagsthatdirtyme above also applies to parents,
	//	so this list is just flags that dirty parent that aren't already included in flagsThatDirtyMe...
	rat.ui.Element.prototype.flagsThatDirtyParent = rat.ui.Element.visibleFlag;
	
	rat.ui.Element.prototype.appendSubElement_unsafe = function (g)
	{
		//	add to sub elements
		if (!this.subElements)
			this.subElements = [g];
		else
			this.subElements.push(g);

		//	and set parent for this subelement
		g.parent = this;
		
		//	fix up tooltip parentage, if needed
		if (g.toolTip && g.toolTipScreenWasAssumed)
			g.toolTipScreen = g.getTopParent();
		
		this.setDirty(true);	//	we're certainly dirty now
		
		//	the thing added to us may need update (or may not understand needsUpdate system),
		//	so give it a chance by reenabling this whole tree's updates for the next time through.
		this.setNeedsUpdate(true);
	};

	//	add sub elements to this element
	rat.ui.Element.prototype.appendSubElement = function (g)
	{
		//	debug:
		if (this.findSubElementByID(g.id, false))
		{
			rat.console.logOnce("WARNING: appending subelement with duplicate ID:  " + g.id, 'dupID');
		}
		
		this.appendSubElement_unsafe(g);
	};

	//	insert sub element in this element, before a given index
	rat.ui.Element.prototype.insertSubElement = function (g, beforeIndex)
	{
		//	debug:
		if (this.findSubElementByID(g.id))
		{
			rat.console.logOnce("WARNING: appending subelement with duplicate ID:  " + g.id, 'dupID');
		}

		if (beforeIndex === void 0)
			beforeIndex = 0;
		//	add to sub elements
		if (!this.subElements)
			this.subElements = [g];
		else
			this.subElements.splice(beforeIndex, 0, g);
		//	and set parent for this subelement
		g.parent = this;
		
		this.setDirty(true);
	};
	
	//	insert sub element before another element
	rat.ui.Element.prototype.insertSubElementBefore = function (g, beforeElement)
	{
		var index = this.getSubElementIndex(beforeElement);
		if (index < 0)
		{
			rat.console.logOnce("ERROR: attempting insert before element not in tree: " + beforeElement.id, 'noBeforeElem');
			index = 0;
		}
		
		this.insertSubElement(g, index);
	};
	
	//	replace a subelement with a new element (same draw order)
	//	Also assume we want it at the same location and size.
	//	todo: support searching our tree for the subpane instead of assuming it's one of our subelements?
	//		maybe another function that first searches and then calls this.
	rat.ui.Element.prototype.replaceSubElement = function (oldElement, newElement)
	{
		var index = this.getSubElementIndex(oldElement);
		if (index < 0)
		{
			rat.console.logOnce("ERROR: replaceSubElement failed to find: " + oldElement.id, 'noReplaceElem');
			return;
		}
		newElement.parent = this;
		newElement.setPos(oldElement.getPos());
		newElement.setSize(oldElement.getSize());
		this.subElements[index] = newElement;
		
		this.setDirty(true);
	};
	
	//	get this element's parent pane
	rat.ui.Element.prototype.getParent = function ()
	{
		return this.parent;
	};
	
	//	get the most-parent parent of this pane.
	//	walk through parentage until we find a pane with no parent.
	rat.ui.Element.prototype.getTopParent = function ()
	{
		var elem = this; // Start from the current element, and find the root element.
		while (elem.parent)
			elem = elem.parent;
		return elem;
	};

	//	remove a sub element by id
	//	including a recursive check
	//	note that this and all the other "removesub" functions don't clear parent ref for the removed item.
	//	todo: should they?  probably...
	rat.ui.Element.prototype.removeSubElementByID = function (id)
	{
		if (this.subElements)
		{
			var i;
			for (i = 0; i < this.subElements.length; i++)
			{
				if (this.subElements[i].id === id)
				{
					//	some cleanup function?
					this.subElements.splice(i, 1);
					this.setDirty(true);
					return true;
				}
			}

			//	not found? look recursively deeper.
			for (i = 0; i < this.subElements.length; i++)
			{
				if (this.subElements[i].removeSubElementByID(id))
				{
					this.setDirty(true);
					return true;
				}
			}
		}

		return false;
	};

	//	remove a sub element by object reference
	//	recursive (see above)
	rat.ui.Element.prototype.removeSubElement = function (element)
	{
		return this.removeSubElementByID(element.id);
	};

	//	remove this element from its own parent
	rat.ui.Element.prototype.removeFromParent = function ()
	{
		if (typeof this.parent !== 'undefined')
			this.parent.removeSubElement(this);
	};

	//	Detach all of my children from me.  Returns the array of gfx
	rat.ui.Element.prototype.detachAllChildren = function ()
	{
		var detached = this.subElements || [];
		this.subElements = void 0;

		for (var index = 0; index !== detached.length; ++index)
			detached[index].parent = null;
		
		this.setDirty(true);
		
		return detached;
	};

	//	remove all subelements of this element
	rat.ui.Element.prototype.removeAllSubElements = function (killMe)
	{
		if (this.subElements)
		{
			for (var i = 0; i < this.subElements.length; i++)
			{
				this.subElements[i].removeAllSubElements(true);	//	kill subelements even if we ourselves are not dying
			}
		}

		if (killMe)
		{
			//console.log("try to kill...");
			//	clear stuff?  animators are sort of external to me, actually.  What do we kill here?
			//	maybe find animators that drive me (look them up) and kill them.  Yeah.  TODO.
			this.killMyAnimators();
		}

		this.subElements = [];
		
		this.setDirty(true);

		//	OLD
		//this.subElements = [];
		//	this is pretty simple for now, but in the future we might need to clear parent refs,
		//	and stuff.  It would be nice to kill animators, for instance, which have callbacks...
		//	which would make this more complex and would possibly need to be recursive?
	};

	rat.ui.Element.prototype.killMyAnimators = function ()
	{
		rat.ui.killAnimatorsForElement(this);
	};

	//	find a sub element by id
	/**
	 * @param {?} id
	 * @param {boolean=} recursive
	 */
	rat.ui.Element.prototype.findSubElementByID = function (id, recursive)
	{
		if (recursive === void 0)
			recursive = true;
		//	search my sub elements.
		var res;
		var elem;
		if (this.subElements)
		{
			for (var i = 0; i !== this.subElements.length; ++i)
			{
				elem = this.subElements[i];
				if (!elem)
					continue;
				if (elem.id === id)
					return elem;
				if (recursive)
				{
					res = this.subElements[i].findSubElementByID(id, recursive);
					if (res)
						return res;
				}
			}
		}

		//	Not found i guess
		return void 0;
	};
	
	//	return a sub element by index
	/**
	 * @param {?} index
	 */
	rat.ui.Element.prototype.getSubElement = function (index)
	{
		if (!this.subElements || this.subElements.length < index)
			return null;
		return this.subElements[index];
	};
	
	//	return the index of this subelement.
	//	Probably an internal-use-only function!  This index is going to be valid only until the list next changes...
	//	return -1 if not found.
	rat.ui.Element.prototype.getSubElementIndex = function (elem)
	{
		if (this.subElements)
		{
			for (var i = 0; i < this.subElements.length; ++i)
			{
				var checkElem = this.subElements[i];
				if (checkElem === elem)
					return i;
			}
		}
		return -1;
	};
	
	//	return the number of my subelements
	rat.ui.Element.prototype.getSubElementCount = function (elem)
	{
		if (this.subElements)
			return this.subElements.length;
			
		return 0;
	};
	
	//	debug - dump info about this element and all subelements.
	//	return object with some extra info like total number of items...
	rat.ui.Element.prototype.dumpTree = function(depth, collectData)
	{
		if (typeof(depth) === 'undefined')
			depth = 0;
		
		var meVisible = this.isVisible();
		
		//	if we didn't get handed collectData, we're presumably first.  Initialize it.
		if (!collectData)
		{
			collectData = {
				lines : [],
				totalCount : 0,
				hiddenCount : 0,
				updateCount : 0,
				parentHidden : false,
			};
		}
		
		//	add my counts
		collectData.totalCount++;
		var oldTotal = collectData.totalCount;
		
		if (this.needsUpdate)
			collectData.updateCount++;
		
		//	if my parent was hidden, I count as hidden in totals
		if (!meVisible || collectData.parentHidden)
			collectData.hiddenCount++;
		
		//	set up my output line to reserve space, but don't fill it out yet.
		var myLineNumber = collectData.lines.length;
		collectData.lines.push("");
		
		//	now collect data from everybody under me
		if (this.subElements)
		{
			//	remember if our parent was hidden, but then set for all my children if I am hidden
			var parentWasHidden = collectData.parentHidden;
			if (!meVisible)
				collectData.parentHidden = true;
			if (this.useOffscreen)	//	if I'm an offscreen render, count my children as being under hidden parent
				collectData.parentHidden = true;
			
			for (i = 0; i < this.subElements.length; i++)
			{
				this.subElements[i].dumpTree(depth+1, collectData);
			}
			
			//	restore old hidden value
			collectData.parentHidden = parentWasHidden;
		}
		
		//	and set up my line
		var out = "";
		var i;
		for (i = 0; i < depth; i++)
		{
			out += "._";
		}
		var bounds = this.getBounds();
		
		//	convert bounds to short strings for more concise display
		bounds.x = "" + rat.math.floor(bounds.x * 100)/100;
		bounds.y = "" + rat.math.floor(bounds.y * 100)/100;
		bounds.w = "" + rat.math.floor(bounds.w * 100)/100;
		bounds.h = "" + rat.math.floor(bounds.h * 100)/100;
		
		//	add xui object subtype if it exists
		//	todo - function instead so other classes can do this as well,
		//	like "getSubTypeString()" or something
		var xuiTypeString = "";
		if (this.xuiElemType)
			xuiTypeString = " " + this.xuiElemType;
		
		//	add total subcount if there was one
		var subCountString = "";
		if (collectData.totalCount > oldTotal)
			subCountString = " subCount: " + (collectData.totalCount - oldTotal);
		
		var visString = (meVisible ? "Visible" : "Hidden");
		if (this.useOffscreen)
			visString = "Offscreen";	//	call this out specifically in the dump - sort of visible
		if (collectData.parentHidden)
			visString = "(" + visString + ")";	//	in some way show that we're actually hidden
		var upString = (this.needsUpdate ? "ups" : "noup");
		collectData.lines[myLineNumber] =
			out
			+ this.id + ":" + this.name
			+ xuiTypeString
			+ " : " + visString
			+ " : " + upString
			+ " : " + bounds.x + ", " + bounds.y + " (" + bounds.w + " x " + bounds.h + ")"
			+ subCountString
		
		//	and we're done
		return collectData;
	};

	//	set generic "color" property.  How this is used depends entirely on subclasses.
	//	The base element class doesn't use it for anything in particular,
	//	and doesn't draw a background by default.
	rat.ui.Element.prototype.setColor = function (c)
	{
		if (typeof(c) === "string")
			c = new rat.graphics.Color(c);
		this.color = c;
	};
	
	rat.ui.Element.prototype.getColor = function ()
	{
		if (this.color)
			return this.color.copy();
		return null;
	};

	rat.ui.Element.prototype.setSize = function (w, h)
	{
		if (w.h !== void 0)	//	unpack size object
		{
			h = w.h;
			w = w.w;
		}
		this.size.x = w;
		this.size.y = h;
		this.boundsChanged();
	};

	rat.ui.Element.prototype.setWidth = function (w)
	{
		this.size.x = w;
		this.boundsChanged();
	};

	/**  Set the position and size of this element
	  *
	 * @param {number|Object} x
	 * @param {number=} y
	 * @param {number=} w
	 * @param {number=} h
	 */
	rat.ui.Element.prototype.setBounds = function (x, y, w, h)
	{
		if (x.x !== void 0)	//	support a single argument which is an object with x,y,w,h
		{
			this.place.pos.x = x.x;
			this.place.pos.y = x.y;
			this.size.x = x.w;
			this.size.y = x.h;
		}
		else	//	handle 4 arguments
		{
			this.place.pos.x = x;
			this.place.pos.y = y;
			this.size.x = w;
			this.size.y = h;
		}
		this.boundsChanged();
	};

	rat.ui.Element.prototype.setHeight = function (h)
	{
		this.size.y = h;
		this.boundsChanged();
	};

	rat.ui.Element.prototype.getSize = function ()
	{
		var theSize = {};
		theSize.x = this.size.x;
		theSize.y = this.size.y;
		theSize.w = this.size.x;	//	alternative names, for convenience
		theSize.h = this.size.y;
		return theSize;
	};

	rat.ui.Element.prototype.getWidth = function ()
	{
		return this.size.x;
	};

	rat.ui.Element.prototype.getHeight = function ()
	{
		return this.size.y;
	};

	//	content size is for managing scroll limits in a scrollview.
	//	most of the time, you want setSize()
	rat.ui.Element.prototype.setContentSize = function (w, h)
	{
		this.contentSize.x = w;
		this.contentSize.y = h;
	};

	//
	//	automatically calculate and set our content size from the position/size of all our subelements.
	//
	rat.ui.Element.prototype.setContentSizeFromSubElements = function ()
	{
		var space = this.calculateContentBounds();
		
		//	intentionally ignoring the potential for space.x and space.y to be other than 0
		//	we're just setting our SIZE here
		this.setContentSize(space.w, space.h);
	};
	
	//	automatically calculate the bounding space of our contained elements.
	//	including factoring in rotation.
	//	This assumes that each subelement bounding box is correct for that element
	rat.ui.Element.prototype.calculateContentBounds = function ()
	{
		var xmin = 9999;
		var xmax = -9999;
		var ymin = 9999;
		var ymax = -9999;
		
		for (var i = 0; this.subElements && i < this.subElements.length; i++)
		{
			var elem = this.subElements[i];
			var bounds = elem.getBounds(elem.tempRect);
			var basePos = elem.place.pos;	//	here's what we'd change if we had a "center"

			//	Handle rotation and scale.
			//if (1)//elem.place.rot.angle != 0)
			{
				//	probably wrong if we have a center offset...
				var cosa = Math.cos(elem.place.rot.angle);
				var sina = Math.sin(elem.place.rot.angle);
				
				//	for each point, transform by rotation of object and find if it changes our min/max x and y
				var checkP = function (x, y)
				{
					var xp = basePos.x + x * cosa - y * sina;
					var yp = basePos.y + x * sina + y * cosa;
					if (xp < xmin) xmin = xp;
					if (xp > xmax) xmax = xp;
					if (yp < ymin) ymin = yp;
					if (yp > ymax) ymax = yp;
				}
				checkP(0, 0);
				checkP(elem.size.x * elem.scale.x, 0);
				checkP(elem.size.x * elem.scale.x, elem.size.y * elem.scale.y);
				checkP(0, elem.size.y * elem.scale.y);
				
			}
		}
		
		return {x:xmin, y:ymin, w:xmax-xmin, h:ymax-ymin};
	};

	//	automatically reset our bounds to include all our content
	rat.ui.Element.prototype.setBoundsFromContent = function (borderSpace)
	{
		var space = this.calculateContentBounds();
		
		//	since space could have negative xy values here, we have to be prepared to
		//	reposition ourselves and shift all our subelements in the opposite direction to match!
		var bumpX = space.x - borderSpace;
		var bumpY = space.y - borderSpace;
		this.setPos(this.place.pos.x + bumpX, this.place.pos.y + bumpY);
		
		for (var i = 0; this.subElements && i < this.subElements.length; i++)
		{
			var elem = this.subElements[i];
			elem.setPos(elem.place.pos.x - bumpX, elem.place.pos.y - bumpY);
		}
		
		this.setSize(space.w + 2 * borderSpace, space.h + 2 * borderSpace);
	};

	rat.ui.Element.prototype.getContentSize = function ()
	{
		return this.contentSize.copy();
	};

	rat.ui.Element.prototype.setPos = function (x, y)
	{
		if (x.x != void 0)	//	unpack from object, if they passed that in
		{
			y = x.y;
			x = x.x;
		}
		this.place.pos.x = x;
		this.place.pos.y = y;
		this.boundsChanged();
	};

	rat.ui.Element.prototype.getPos = function ()
	{
		return this.place.pos;	//	note - returning a REF... usually they'll want to call getPos().copy()
	};

	rat.ui.Element.prototype.getPosX = function () { return this.place.pos.x; };
	rat.ui.Element.prototype.getPosY = function () { return this.place.pos.y; };

	rat.ui.Element.prototype.setPosX = function (x)
	{
		this.place.pos.x = x;
		this.boundsChanged();
	};
	rat.ui.Element.prototype.setPosY = function (y)
	{
		this.place.pos.y = y;
		this.boundsChanged();
	};

	//	Set this ui element's scale.
	rat.ui.Element.prototype.setScale = function (x, y)
	{
		this.scale.x = x;
		this.scale.y = y;
		
		//	This doesn't change my bounds.  And for most (all?) cases, that's fine.
		//	Scaling at this level happens without an element knowing about it, generally.
		//	We just apply a context scale and then draw the element normally,
		//	so the bounds of the element didn't change, from the element's point of view.
		
		//	similarly, we don't set a dirty flag here for the element itself,
		//	because it doesn't change the rendering of that element.
		//	Scaled content does, however, change the look of the element that contains it,
		//	so the parent element needs to be set dirty.
		//	Same concept applies below in setting rotation and opacity and stuff...
		if (this.parent)
			this.parent.setDirty(true);
	};

	rat.ui.Element.prototype.getScale = function ()
	{
		return this.scale;
	};

	rat.ui.Element.prototype.setRotation = function (angle)
	{
		this.place.rot.angle = angle;
		
		//	see setScale notes above
		if (this.parent)
			this.parent.setDirty(true);
	};

	rat.ui.Element.prototype.getRotation = function ()
	{
		return this.place.rot.angle;
	};

	rat.ui.Element.prototype.setOpacity = function (alpha)
	{
		this.opacity = alpha;
		
		//	see setScale notes above
		if (this.parent)
			this.parent.setDirty(true);
	};
	
	rat.ui.Element.prototype.getOpacity = function ()
	{
		return this.opacity;
	};

	rat.ui.Element.prototype.setOpacityRecursive = function (alpha)
	{
		this.applyRecursively(rat.ui.Element.prototype.setOpacity, alpha);
	};

	rat.ui.Element.prototype.setID = function (id)
	{
		this.id = id;
	};

	/**
	 * Set the frame on this element
	 * @param {number} frameWidth how wide is the frame
	 * @param {Object=} frameColor
	 * @param {?} frameOutset
	 */
	rat.ui.Element.prototype.setFrame = function (frameWidth, frameColor, frameOutset)
	{
		this.frameWidth = frameWidth;
		if (typeof frameColor !== 'undefined')
		{
			if (typeof frameColor === 'string')	//	support style string
				this.frameColor.copyFrom(rat.graphics.Color.makeFromStyleString(frameColor));
			else
				this.frameColor.copyFrom(frameColor);
		}
		else if( frameColor === void 0 )
			this.frameColor.copyFrom(rat.graphics.white);
		
		if (typeof frameOutset !== 'undefined')
			this.frameOutset = frameOutset;
		
		//	we consider "frame" rendering to happen outside offscreen buffers,
		//	so, this does not mark US as dirty, but we do need to re-render our parent.
		if (this.parent)
			this.parent.setDirty(true);
	};

	rat.ui.Element.prototype.setFrameRandom = function (frameWidth)
	{
		this.frameWidth = frameWidth;
		this.frameColor.setRandom();
		//	leave outset whatever it was
		
		//	see setFrame notes above
		if (this.parent)
			this.parent.setDirty(true);
	};

	/**
	//	get global coordinates from local coordinates relative to me.  Compare with getGlobalContentPos below.
	//	this involves processing the chain from parent to parent, to the top level.
	//	But we do that locally, instead of recursively, to avoid extra function calls and overhead.
	* @param {number=} x
	* @param {number=} y
	*/
	rat.ui.Element.prototype.getGlobalPos = function (x, y)
	{
		if (x === void 0)
		{
			x = 0;
			y = 0;
		}
		if (x.x !== void 0)	//	support object being passed in
		{
			y = x.y;
			x = x.x;
		}

		var pane = this;
		do
		{
			//	2016.5.23 STT:
			//		for things inside things that are centered, I really think getGlobalPos needs to factor in centering.
			//		otherwise there's no way this function will return the right value.  Who knows which of these items in the chain
			//		have their own centering value.
			//		it's *possible* it should only be in the "parent" check below?  I'm not sure.
			//		anyway, it was in neither place, before, which really seems wrong.
			//		I had to change getGlobalBounds() to match this...
					
			x -= pane.center.x;
			y -= pane.center.y;
			
			//	factor in my scale
			x *= pane.scale.x;
			y *= pane.scale.y;
			
			//	move to parent space
			x += pane.place.pos.x;
			y += pane.place.pos.y;

			if (pane.parent)
			{
				//	factor in scrolled/scaled content
				x *= pane.parent.contentScale.x;
				y *= pane.parent.contentScale.y;
				x += pane.parent.contentOffset.x;
				y += pane.parent.contentOffset.y;
			}
			pane = pane.parent;
		} while (pane);
		
		//return new rat.Vector(x, y);
		return {x:x, y:y};
	};

	//	get global coordinates from a point inside my content.
	//	This is different from above if MY content itself is scrolled.
	//	So, this is useful mostly for scrollview content
	rat.ui.Element.prototype.getGlobalContentPos = function (x, y)
	{
		if (typeof x === 'undefined')
		{
			x = 0;
			y = 0;
		}
		//	factor in scrolled/scaled content
		x *= this.contentScale.x;
		y *= this.contentScale.y;
		x += this.contentOffset.x;
		y += this.contentOffset.y;
		
		return this.getGlobalPos(x, y);
	};

	//	convert parent-space point to local space point.
	//	this factors in:
	//		my location inside parent
	//		my scale, if any
	rat.ui.Element.prototype.parentToLocalPos = function (x, y)
	{
		var relPos = new rat.Vector(x, y);
		relPos.x -= (this.place.pos.x);// - this.center.x);
		relPos.y -= (this.place.pos.y);// - this.center.y);
		
		//	Why do we not factor in centering here?  I don't know.  Should have commented this...
		//	2016.5.23 I feel like this (centering) should maybe be rethought and added back in.
		//	But centering as a while all needs to be rethought.

		//	factor in my scale
		//	(this is a divide because we draw scaled up,
		//		so a point on the screen is bigger than logical points inside me and my subelements,
		//		who know nothing about my scale)
		relPos.x /= this.scale.x;
		relPos.y /= this.scale.y;

		return relPos;
	};

	//	convert parent-space point to local content space point.
	//	The difference here is that we factor in our content scroll, which is useful for scrollviews.
	//	So, this factors in:
	//		my location inside parent
	//		my scale
	//		my content scroll, if any
	//		my content scale, if any
	rat.ui.Element.prototype.parentToLocalContentPos = function (x, y)
	{
		var pos = this.parentToLocalPos(x, y);

		pos.x -= this.contentOffset.x;
		pos.y -= this.contentOffset.y;
		pos.x /= this.contentScale.x;
		pos.y /= this.contentScale.y;
		
		return pos;
	};
	
	//	TODO:  Why are there no globalToLocalPos and globalToLocalContentPos functions?
	//	they're tricky, but need to exist.  Walk through parent chain from global to self, converting space using parentToLocal function.
	//	probably do this with postorder recursion.
	
	//	convert from global space to my own local space.
	rat.ui.Element.prototype.globalToLocalPos = function (x, y)
	{
		var relPos;
		if (this.parent)
			relPos = this.parent.globalToLocalPos(x, y);
		else
			relPos = {x:x, y:y};

		relPos.x -= (this.place.pos.x);// - this.center.x);
		relPos.y -= (this.place.pos.y);// - this.center.y);
		//	Why do we not factor in centering here?  I don't know.  Should have commented this...
	
		//	factor in my scale
		relPos.x /= this.scale.x;
		relPos.y /= this.scale.y;
		
		return relPos;
	};
	
	//
	//	Utility function to apply this function (as a "call") to all subelements recursively (with arg), including this one.
	//	If any element returns non-false it means it handled everything, and we should stop calling.
	//
	//	NOTE:  This will call this specific function on each element, but won't handle polymorphism!  If you just want to apply a utility function, great.
	//		if you want to give every subpane a chance to override and handle in their own way, this is not the approach you want.
	//	We could support that by using a function NAME instead of a function.  That'd be a different utility, I think, though similar to this.
	//
	//	todo: use this in more places?
	//	todo: varargs
	/**
	 * @param {?} func
	 * @param {?} arg
	 */
	rat.ui.Element.prototype.applyRecursively = function (func, arg)
	{
		//	do my own handling
		var res = func.call(this, arg);
		if (res)
			return res;

		//	now handle for all children
		if (this.subElements)
		{
			for (var i = 0; i < this.subElements.length; i++)
			{
				//	call this recursive utility on each subelement
				res = this.subElements[i].applyRecursively(func, arg);
				if (res)
					return res;
			}
		}
		return false;
	};

	//
	//	Utility function to pass this call down to all subelements,
	//	This is not inherently recursive, but will recurse if the applied function also calls this function again, which is often the case.
	//
	rat.ui.Element.prototype.callForSubElements = function (func, arg)
	{
		if (this.subElements)
		{
			for (var i = 0, len = this.subElements.length; i !== len; ++i)
			{
				var res = func.call(this.subElements[i], arg);
				if (res)
					return res;
			}
		}
		return false;
	};

	//
	//	Utility function to pass this call down to all subelements,
	//	with relative pos calculated...
	//	This is not inherently recursive, but will recurse if the applied function also calls this function again, which is often the case.
	//
	//	If any sub element claims to have handled this call, then stop calling, and return handled.
	//	So, we're calling until somebody says they handled this action.
	//
	//	TODO:  There must be a better way to do all this in JS.
	//		at the very least, use varargs here.
	//
	rat.ui.Element.prototype.callForSubElementsWithPos = function (func, pos, arg1, arg2)
	{
		if (this.subElements)
		{
			//	let subelements handle this.  Make sure they're thinking in MY coordinates (parent, to them)
			//	and include my scroll state, if any, since subelements are part of my contents

			var relPos = this.parentToLocalContentPos(pos.x, pos.y);

			//	factor in centering.
			//	why is this not part of parentToLocalContentPos?  I'm not sure,
			//	maybe it should be, but I don't want to screw other stuff up,
			//	without testing everything again and that's a drag...
			relPos.x += this.center.x;
			relPos.y += this.center.y;
			
			// Handle the elements front to back, in case any of our subelements get deleted while being processed.
			for (var i = this.subElements.length - 1; i >= 0; i--)
				//for (var i = 0; i < this.subElements.length; i++)
			{
				var res = func.call(this.subElements[i], relPos, arg1, arg2);
				if (res)
					return res;
			}
		}
		return false;
	};

	//
	//	update me and my subelements
	//	(e.g. animate sprites, jiggle, whatever.)
	//	compare and contrast with animator class...
	//	this is for more internal class-specific animation, like changing my internal appearance over time.
	//	animator class is for pushing around elements externally.
	//
	rat.ui.Element.prototype.update = function (dt)
	{
		rat.ui.updateCallCount++;	//	debug
		
		//	let's hope nobody (me, and my children) needed an update, and we'll correct that assumption below if needed.
		//	the only way for my needsUpdate flag to get turned off is if my own update functions (updateSelf, updateSelfPost)
		//	report back that they didn't need an update, and my children also don't need one.
		var neededUpdate = false;
		
		//	if I have an updateself, call that.  (convenience for subclasses and game use)
		if (this.updateSelf)
		{
			var res = this.updateSelf(dt);
			if (res === void 0 || res === true)	//	either doesn't understand update system or explicitly needs update
				neededUpdate = true;
		}

		//	New approach:  don't use callForSubElements, do my own loop.
		//	update() is taking a lot of time in some games.  I'd like to minimize this.
		//	More importantly, we now do some flag checking as we loop through...
		if (this.subElements)
		{
			var len = this.subElements.length;
			for (var i = len-1; i >= 0; --i)	//	reverse in case some are removed from list
			{
				var e = this.subElements[i];
				if (e.needsUpdate)
				{
					var res = e.update(dt);
					if (res === void 0 || res === true)	//	either doesn't understand update system or explicitly needs update
						neededUpdate = true;
				}
			}
		}
		
		//	old way
		//this.callForSubElements(rat.ui.Element.prototype.update, dt);
		
		if (this.toolTip)
		{
			//	let's assume if we have a tooltip at all, we need to update it frequently,
			//	to check state changes.
			//	TODO:  Fix this.  It's not particularly optimal.
			//	We should turn updating on only when mouse is being tracked inside element,
			//	and turn it off when mouse leaves element.
			neededUpdate = true;
			this.updateToolTip(dt);
		}

		//	updateSelfPost (optional, of course) is for panes to update after their subpanes have updated.
		if (this.updateSelfPost)
		{
			var res = this.updateSelfPost(dt);
			if (res === void 0 || res === true)	//	either doesn't understand update system or explicitly needs update
				neededUpdate = true;
		}

		//	OK, finally, if we got through that without anybody needing an update (or just not really telling us)
		//	let's turn off our needsUpdate flag.
		//	This will stay off until somebody explicitly sets it again,
		//	or calls setNeedsUpdate() on us or a child.
		if (!neededUpdate)
			this.needsUpdate = false;
		
		return this.needsUpdate;
	};
	
	//	needs update tracking
	rat.ui.Element.prototype.setNeedsUpdate = function (needs)
	{
		if (needs === void 0)
			needs = true;
		this.needsUpdate = needs;
		
		if (!needs)	//	if clearing, just apply to us
			return;
		
		//	this also means my whole parent chain needs update.
		//	do this all directly in a loop here to save some time
		var e = this.parent;
		while (e)
		{
			e.needsUpdate = true;
			e = e.parent;
		}
	};

	//	calculate anchor (center) position based on size.
	//	This is one-time - it doesn't auto-update later.
	//	if you pass "false" in here, it resets centering to topleft
	rat.ui.Element.prototype.autoCenter = function (doCenter)
	{
		if (doCenter || doCenter === void 0)
		{
			this.center.x = this.size.x / 2;
			this.center.y = this.size.y / 2;
		} else {
			this.center.x = this.center.y = 0;
		}
	};
	
	rat.ui.Element.prototype.setCenter = function (x, y)
	{
		this.center.x = x;
		this.center.y = y;
		
		//	see setScale notes above
		if (this.parent)
			this.parent.setDirty(true);
	};

	//	assuming our size is correct, center this pane at this position.
	//	(the point given needs to be in my parent space)
	//	this does NOT use the "center" property, it just positions us based on our size.
	rat.ui.Element.prototype.centerAt = function (atX, atY)
	{
		var x = atX - this.size.x / 2;
		var y = atY - this.size.y / 2;
		this.setPos(x, y);
	};
	
	//	assuming our size is correct, center us in parent
	//	this does not use the "center" property, it just positions us based on our size and parent size.
	rat.ui.Element.prototype.centerInParent = function ()
	{
		if (!this.parent)
			return;

		//console.log("centerInParent: " + this.size.x + " p " + this.parent.size.x);

		this.place.pos.x = (this.parent.size.x - this.size.x) / 2;
		this.place.pos.y = (this.parent.size.y - this.size.y) / 2;
		
		//	see setScale notes above
		this.parent.setDirty(true);
	};

	//	assuming our size is correct, center us horizontally in parent
	rat.ui.Element.prototype.centerInParentHorizontally = function ()
	{
		if (!this.parent)
			return;

		this.place.pos.x = (this.parent.size.x - this.size.x) / 2;
		
		this.parent.setDirty(true);
	};

	//	assuming our size is correct, center us vertically in parent
	rat.ui.Element.prototype.centerInParentVertically = function ()
	{
		if (!this.parent)
			return;

		this.place.pos.y = (this.parent.size.y - this.size.y) / 2;
		
		this.parent.setDirty(true);
	};

	//
	//	resize to parent size.
	//	todo maybe rename this function to resizeToParent, or fitToParentSize or something?
	//	why "auto"?  Well, it does seem to be the first thing I think of when I imagine this function...  maybe leave it.
	rat.ui.Element.prototype.autoSizeToParent = function ()
	{
		if (!this.parent)
			return;

		//console.log("autoSizeToParent: " + this + " p " + this.parent);
		this.setBounds(0, 0, this.parent.size.x, this.parent.size.y);
	};

	rat.ui.Element.prototype.autoSizeToContent = function ()
	{
		this.size.x = this.contentSize.x;
		this.size.y = this.contentSize.y;

		this.boundsChanged();
	};

	//	get my bounds in parent space
	//	Factor in my own scale automatically, if I'm scaled, because various things like tooltip highlights make more sense this way.
	rat.ui.Element.prototype.getBounds = function (dest)
	{
		var r = dest || new rat.shapes.Rect();
		r.x = this.place.pos.x - this.center.x;
		r.y = this.place.pos.y - this.center.y;
		r.w = this.size.x * this.scale.x;
		r.h = this.size.y * this.scale.y;
		return r;
	};
	//	old name for compatibility
	//rat.ui.Element.prototype.getMyBounds = rat.ui.Element.prototype.getBounds;

	//	get my local bounds (x and y are always 0)
	rat.ui.Element.prototype.getLocalBounds = function ()
	{
		var r = new rat.shapes.Rect(0 - this.center.x, 0 - this.center.y, this.size.x, this.size.y);
		return r;
	};
	//	old name for compatibility
	rat.ui.Element.prototype.getMyLocalBounds = rat.ui.Element.prototype.getLocalBounds;

	//	get my global bounds
	rat.ui.Element.prototype.getGlobalBounds = function ()
	{
		var pos = this.getGlobalPos();
		
		//	these approaches are problematic.  For one thing, they don't factor in scale.. :)
		//var r = new rat.shapes.Rect(pos.x - this.center.x, pos.y - this.center.y, this.size.x, this.size.y);
		//var r = new rat.shapes.Rect(pos.x, pos.y, this.size.x, this.size.y);
		
		var botRight = this.getGlobalPos(this.size.x, this.size.y);
		
		var r = new rat.shapes.Rect(pos.x, pos.y, botRight.x - pos.x, botRight.y - pos.y);
		
		return r;
	};

	//
	//	adjust this bounds variable for use in testing touch/move in/out of bounds.
	//	this is different because for some events we'll want to factor in fat fingers
	//
	rat.ui.Element.prototype.adjustBoundsForPointer = function (bounds, ratEvent)
	{
		if (ratEvent && ratEvent.isFromTouch)
		{
			var radX = ratEvent.touchRadiusX;
			var radY = ratEvent.touchRadiusY;
			bounds.x -= radX;
			bounds.y -= radY;
			bounds.w += radX * 2;
			bounds.h += radY * 2;
		}
		return bounds;
	};
	
	//	find deepest subelement that includes this point.
	//	return null if it's not even in me.
	//	return subelement (or sub-sub...) that contains this point.
	//	otherwise return self 
	//	the "pos" passed in is assumed to be in my parent space (e.g. relative to my pos!)
	rat.ui.Element.prototype.findSubElementByPoint = function(pos, requireFlags, ratEvent)
	{
		//	Search subelements first.
		//	Note that we intentionally aren't checking our own bounds first.
		//	I'm chosing to make this mean that we find ANY pane that's in the right place visually,
		//	and not restrict things being properly inside parent bounds to qualify.
		
		//	Make sure subelements are thinking in parent-relative coordinates (we are the parent in that case)
		var relPos = this.parentToLocalContentPos(pos.x, pos.y);
		//	adjust relpos to factor in centering.  We factored it in above in getBounds(),
		//	but need to do it here as well so all sub elements are working with the right position, if we have a centering offset.
		//	why is this not part of parentToLocalContentPos?  I don't know.
		relPos.x += this.center.x;
		relPos.y += this.center.y;
		
		var foundSub = null;
		if (this.subElements)
		{
			for (var i = this.subElements.length-1; i >= 0; i--)
			{
				var elem = this.subElements[i];
				if ((elem.flags & requireFlags) === requireFlags)
				{
					var foundSub = elem.findSubElementByPoint(relPos, requireFlags);
					if (foundSub)
						break;
				}
			}
		}
		if (foundSub)
			return foundSub;	//	was in some sub element
		
		//	in me, at least?
		var myBounds = this.getBounds(this.tempRect);
		if (ratEvent)
			this.adjustBoundsForPointer(myBounds, ratEvent);
		var inBounds = rat.collision2D.pointInRect(pos, myBounds);
		if (!inBounds)
			return null;	//	not in me or my subelements
		
		return this;	//	OK, was at least in me
	};

	//	The cursor newly entered my bounds (regardless of pressed or not)
	//	(called for each element from handleMouseMove below)
	//	Note that this is only called if the mouse is not already inside this element.
	rat.ui.Element.prototype.mouseEnter = function ()
	{
		//console.log("..enter " + this.name);

		if ((this.flags & rat.ui.Element.enabledFlag) === 0)	//	don't process if we're disabled
			return;

		var oldFlags = this.flags;
		this.flags |= rat.ui.Element.mouseInFlag;

		if (this.flags & rat.ui.Element.trackingMouseDownFlag)
		{
			//	pressed state happens again if we were tracking mousedown
			//	e.g. we clicked in, moved out, and moved back in without lifting mouse.
			this.flags |= rat.ui.Element.pressedFlag;
		}
		else
		{
			//console.log("..high " + this.name);
			//	if we were not already tracking a click, use highlight state to highlight that this is clickable
			this.flags |= rat.ui.Element.highlightedFlag;
		}
		this.checkFlagsChanged(oldFlags);

	};

	//	mouse left my bounds, regardless of pressed or not
	//	(called for each element from handleMouseMove below)
	rat.ui.Element.prototype.mouseLeave = function ()
	{
		if ((this.flags & rat.ui.Element.enabledFlag) === 0)	//	don't process if we're disabled
			return;

		//if (this.isVisible())
		//	console.log("..leave " + this.name);
		var oldFlags = this.flags;
		
		this.flags &= ~rat.ui.Element.mouseInFlag;

		//	only unhighlight if we were not tracking a click
		if ((this.flags & rat.ui.Element.trackingMouseDownFlag) === 0)
		{
			//console.log("..unhigh " + this.name);
			this.flags &= ~rat.ui.Element.highlightedFlag;
		}
		this.flags &= ~rat.ui.Element.pressedFlag;	//	not pressed if moved outside
		
		this.checkFlagsChanged(oldFlags);
	};

	//
	//	mouse clicked down in me.
	//	(only called if mouse down happened in my bounds)
	//	pos is in LOCAL space to make local logic easier for classes in this module and for user subclasses
	//	(called for each element from handleMouseDown below)
	//
	//	This is often overridden in subclasses,
	//	but this behavior here is commonly needed - set tracking/pressed flags.
	rat.ui.Element.prototype.mouseDown = function (pos, ratEvent)
	{
		if ((this.flags & rat.ui.Element.enabledFlag) === 0)	//	don't process if we're disabled
			return false;

		if ((this.flags & rat.ui.Element.tracksMouseFlag) === 0)	//	or if we don't track mouse at all...
			return false;
			
		var oldFlags = this.flags;
		this.flags |= rat.ui.Element.trackingMouseDownFlag;
		this.flags |= rat.ui.Element.pressedFlag;
		
		//	in case we're targetable, and we're in a screen of some kind, become target!
		this.targetMe(ratEvent);
		
		this.checkFlagsChanged(oldFlags);

		//	return whether we handled this mouse down or not.
		//	Not sure what counts as "handled" in this case...
		//	Currently, we let multiple panes track a mousedown,
		//		especially in the case of a container and its subpanes.
		//	So, let's not claim this mousedown event as exclusively ours.
		return false;
	};

	//	mouse up
	//	called whether the mouseup happened inside this element's bounds or not.
	//	(in case we were tracking)
	//	pos is in LOCAL space to make local logic easier for classes in this module and for user subclasses
	//	(called for each element from handleMouseUp below)
	//	TODO:
	//		If this mouseup event is from touch (ratEvent.isFromTouch, right?)
	//		then after processing this, fake a mousemove event of some kind
	//		(e.g. to -1,-1) since we no longer know where the mouse is.
	//		Some people might want to know where the most recent mouse was, though?
	//		maybe instead leave position where it is, but send a mouseleave event everywhere?
	//		something.
	//		Maybe do this at a higher level, like an earlier dispatcher.
	//		We have specific unhighlight code below in the case of touch, for this object,
	//		but what about anybody else who was tracking the mouse, or otherwise thinks they know
	//		where it is?
	rat.ui.Element.prototype.mouseUp = function (pos, ratEvent)
	{
		if ((this.flags & rat.ui.Element.enabledFlag) === 0)	//	don't process if we're disabled
			return false;

		var oldFlags = this.flags;

		//	clear relevant flags.
		//  Do this before trigger() below, in case some distant function cares about our flags.
		//  For instance (complicated but likely scenario), a new window is popped up,
		//      and we try to unhighlight this button with mouseLeave(), which checks trackingMouseDownFlag.
		this.flags &= ~rat.ui.Element.trackingMouseDownFlag;
		this.flags &= ~rat.ui.Element.pressedFlag;
		
		//	highlighting and mousein flag are tricky.  See notes above,
		//	but what we're going to do now is just deal with them differently
		//	based on whether this was a touch event or not.
		if (ratEvent.isFromTouch)
		{
			this.flags &= ~rat.ui.Element.highlightedFlag;
			this.flags &= ~rat.ui.Element.mouseInFlag;
			//	If touch, then sure, cursor is not in.
			//	if mouse-device, then cursor is definitely still in.
		}
		
		//	were we tracking a click?
		var handled = false;
		if (oldFlags & rat.ui.Element.trackingMouseDownFlag)
		{
			//rat.console.log("tmouseup in... " + this.id + " | " + this.name);
			
			var myBounds = this.getLocalBounds();
			this.adjustBoundsForPointer(myBounds, ratEvent);

			//	and was this mouseup in our bounds?  If so, trigger!
			if (rat.collision2D.pointInRect(pos, myBounds))
			{
				//	...  All elements fire a triggered event when they get a mouse up...
				//	But most don't do anything about it...  How to know
				handled = this.trigger();
				if (handled)
					rat.eventMap.fireEvent("uiTriggered", this);
			}
			else	//	were tracking, they let up outside
			{
				//  unhighlight, since they were outside.
				this.flags &= ~rat.ui.Element.highlightedFlag;
				
				//	We were tracking this - probably should return true here.
				//	(this was our mouse input, and we did handle it)
				//handled = true;
				//	UGH.
				//	Weird problems with this.
				//	Do we let multiple elements track a single click?
				//	e.g. container and buttons inside it?
				//	If so, we CAN'T return handled here,
				//	 or we potentially leave the trackingMouseDown flag set for our subelements,
				//	since returning true means we stop processing this mouseup,
				//	and then the NEXT mouseup will handle this.
				//	TODO: only let one element ever track clicks?
				//	IF SO, then fix a bunch of things:
				//		* return true here
				//		* change tracksmouse flag to default to OFF for most things
				//		* even if a container has tracksmouse false, let its subelements see if they do...
				//		* once ANY element starts tracking a single click, return handled from mousedown,
				//			so nobody else sets their tracking flag.
				//		It has to be one way or the other:	we can't stop processing mouseups if we let multiple people process mousedowns.
				handled = false;
			}
		}

		this.checkFlagsChanged(oldFlags);
		
		//	STT TODO this may need some research and testing here.
		//	Do we sometimes end up tracking mousedown in multiple panes at once, if one's inside another?
		//	does trackingMouseDownFlag get set on more than one pane?
		//	if so, and we return true for any reason above,
		//	does the other pane (e.g. our parent) ever find out it's not tracking any more?
		//	returning true stops us from handing mouseup UP the visual tree...
		
		return handled;
	};

	//
	//	Handle mouse movement to new position.
	//	pass down to subelements.
	//	this position is relative to our parent (parent space)
	//		"pos" is passed in separately from the event so it can be modified locally
	//			(e.g. change coordinate systems) without modifying the original event, and passed down recursively
	//
	//	TODO: correctly deal with "handled" for this and related mousemove functions, like other events.
	//
	//	TODO:  handleMouseMove is called for EVERY element in a screen, currently, which can get really slow,
	//		and affects performance on low-end machines in a painful way.
	//		We need to find a way to support all the funky leave stuff below without even calling this function for
	//		elements that don't care, e.g. they weren't already tracking.  Maybe some "trackingMouse" flag, that's
	//		a superset of trackingMouseDown?
	//		Actually, what we need is a screen-level 'care about mousemove' list, and a set of changes like this:
	//			mousemove should only be called for
	//				+ elements that mouse is really inside (inBounds)
	//				+ and elements in a special 'care about mousemove' list.
	//			elements get added to that list when the mouse moves inside them (e.g. mouseEnter is called)
	//			elements get removed from that list when they decide to let go
	//				(by default, on mouseLeave, but some classes can override)
	//				(e.g. thumb in scrollbar)
	//			elements that get removed from tree or deleted need to get removed from that list - how to do that?
	//				maybe auto-clean-up from that list of we go a frame without calling an element's handleMouseMove? ('cause it's not in the tree)
	//				yes, an auto-clean-up approach seems most robust.
	//			The reason it needs to be a list is that tracking might need to happen deep down the tree, inside a group.
	//				and we'd need to traverse the whole tree to even get to those items, without filtering by inBounds,
	//				if we didn't have a separate list.  Traversing the whole tree and checking every object is what's already causing performance problems.
	//			Note that it should be a list of lists, to handle multiple simultaneous input (e.g. finger) movements, in the future.
	//			Also note that Mouse UP should go through this same prioritized list of handlers.
	//		Actually, I'm no longer sure of this.  It could be really complicated.
	//		And is this the biggest problem we need to solve right now?
	//
	rat.ui.Element.prototype.handleMouseMove = function (newPos, handleLeaveOnly, ratEvent)
	{
		if (!this.isVisible())	//	don't handle mouse if I'm invisible - what do they think they're clicking on or hovering over?
			return false;

		rat.ui.mouseMoveCallCount++;	//	debugging
		
		//	handleLeaveOnly is a way to let subpanes (remember this is a recursive function) let go of mouse tracking, if they need to,
		//	but don't start any new tracking.  This is to support things like items inside a scrollview, and the mouse is inside, then outside the scrollview.
		if (typeof (handleLeaveOnly) === 'undefined')
			handleLeaveOnly = false;

		/** @todo	correctly handle rotation?
 */

		var myBounds = this.getBounds(this.tempRect);
		this.adjustBoundsForPointer(myBounds, ratEvent);
		var inBounds = rat.collision2D.pointInRect(newPos, myBounds);

		if (!handleLeaveOnly && inBounds && (this.flags & rat.ui.Element.mouseInFlag) === 0)
			this.mouseEnter();
		if (handleLeaveOnly || (!inBounds && (this.flags & rat.ui.Element.mouseInFlag)))
			this.mouseLeave();

		//	let all subelements handle this, regardless of mouse position.
		//	? Unless we've got clipping on, and this movement is outside my space
		//	in which case the user can't see the thing reacting, so don't even try it.
		//	TODO:  ignore clip flag?  Just never pass down if this move is outside our space?
		//	This still not ideal.  elements that were tracking mouse should get a chance to realize they're not tracking it anymore,
		//		but it depends on element behavior...  See store screen in agent for example.
		//	Also, imagine a scrollbar thumb the user has clicked on, and we want it to track that click until they let go,
		//		regardless of where they drag their mouse.  Gotta keep calling handleMouseMove in that case.
		//if (!inBounds && (this.flags & rat.ui.Element.clipFlag) != 0)
		//	return;
		if (!inBounds && (this.flags & rat.ui.Element.clipFlag) !== 0)
			handleLeaveOnly = true;

		//	Make sure subelements are thinking in parent-relative coordinates (we are the parent in that case)
		var relPos = this.parentToLocalContentPos(newPos.x, newPos.y);

		//	remember that relative pos for later calculations relative to mouse position, like tooltips
		this.mousePos = relPos;

		//	if we have a local mouseMove function, call that now, using local coordinates..
		//	why do we check this here, and not check mouseDown and mouseUp?  inconsistent...?
		//	todo: should center already be factored in here?  PROBABLY!
		//	I haven't tested it.
		//	if it does, just move the relPos adjustment line up before here...
		if (this.mouseMove)
		{
			//	factor in handleLeaveOnly here?
			this.mouseMove(relPos, ratEvent);
		}
		//	adjust relpos to factor in centering.  We factored it in above in getBounds(),
		//	but need to do it here as well so all sub elements are working with the right position, if we have a centering offset.
		relPos.x += this.center.x;
		relPos.y += this.center.y;

		//	why not using callForSubElementsWithPos like others?  Probably should?
		//	Note that callForSubElementsWithPos does the offset/scale calculation above, so we'd pass in newPos in that case...
		//	and it does the center offset as well, now...
		//	and we've have to add a second arg, or support varargs (see callForSubElementsWithPos)
		//	Also, we're changing the behavior a bit, checking some flags here for speed.
		if (this.subElements)
		{
			//	walk through backwards, so elements that draw on top get processed first.
			for (var i = this.subElements.length-1; i >= 0; i--)
			{
				var elem = this.subElements[i];
				if (elem.flags & rat.ui.Element.tracksMouseFlag)
					elem.handleMouseMove(relPos, handleLeaveOnly, ratEvent);
			}
		}
	};
	
	//	OK, several positional events (mousedown, contextmenu, mousewheel)
	//	all behave the same way:
	//		check visibility and clip
	//		recursively look through panes
	//		if the event happened inside a pane, call its pane-specific handler function, if there is one.
	//	So, collect that functionality here!
	//	This is tricky, because we also want subclasses to be able to override
	//		both the tree event handling function (e.g. handleMouseWheel) and the specific pane-space function (e.g. mouseWheelEvent)
	//		so we do that with the function references here.
	//	Also, note that unlike some other event handling, here we're going to say if somebody handles this event,
	//	STOP processing it!
	//	SCENARIO:
	//		in the good editor, you have two panes on top of each other, e.g. two overlapping text panes.
	//		right-clicking should not pop up TWO context menus.  Just one.  So, as soon as somebody handles the right-click
	//		and returns true (handled), stop processing.
	rat.ui.Element.prototype.handlePositionalEvent = function(pos, ratEvent, forChildFunc, forMeFunc)
	{
		//	don't handle positional events if I'm invisible - what do they think they're clicking/acting on?
		if (!this.isVisible())
			return false;
		
		var myBounds = this.getBounds(this.tempRect);
		this.adjustBoundsForPointer(myBounds, ratEvent);
		var inBounds = rat.collision2D.pointInRect(pos, myBounds);
		
		var handled = false;
		//	If clipping is not on, some containers have lazily-defined bounds and we need to let them handle this anyway,
		//	even if technically it's not in their bounds.
		//	If we have real clipping turned on, and it's outside our space, then don't pass down at all.
		//	The logic is this:  If we have real clipping turned on, our bounds have to be accurate,
		//	so it's OK to pay attention to that,
		//	and the user can't see anything outside those bounds anyway, since it's clipped during draw,
		//	So, only pass down clicks if we're in bounds or not clipped.
		//	NOTE:  we should probably just require this all the time and stop babying containers that have been set up wrong...
		if (this.subElements && (inBounds || (this.flags & rat.ui.Element.clipFlag) === 0))
		{
			//	let subelements handle this.  Make sure they're thinking in MY coordinates (parent, to them)
			//	and include my scroll state, if any, since subelements are part of my contents

			var relPos = this.parentToLocalContentPos(pos.x, pos.y);

			//	factor in centering.
			//	why is this not part of parentToLocalContentPos?  I'm not sure,
			//	maybe it should be, but I don't want to screw other stuff up,
			//	without testing everything again and that's a drag...
			relPos.x += this.center.x;
			relPos.y += this.center.y;
			
			// Handle the elements front to back, in case any of our subelements get deleted while being processed.
			//	this is probably good anyway, since we want front panes to react first, since they draw on top.
			for (var i = this.subElements.length - 1; i >= 0; i--)
			{
				var res = forChildFunc(this.subElements[i], relPos, ratEvent);
				if (res)
					return true;
				//	handled = true;
				//	return?  see other thinking about whether this should interrupt...
			}
		}
		
		if (inBounds && !handled)
		{
			//	Convert to local coordinates
			//	Ignore scroll for myself - this position is in "my" space, but not my scrolled content space - worry about that below
			var localPos = this.parentToLocalPos(pos.x, pos.y);
			handled = forMeFunc(this, localPos, ratEvent);
		}
		
		return handled;
	}
	
	//
	//	Handle and pass on mouse down event, by calling appropriate mouseDown function
	//	for whatever pane was clicked in...
	//
	//	todo: maybe change this to postOrder, and let an element say it was handled,
	//		and if it was handled, don't keep passing down.
	//
	//	This is split into two functions (handleMouseDown and mouseDown) so most subclasses
	//	don't have to worry about the recursive logic here, they just implement mouseDown if they care
	//	about clicks inside their space.
	//
	//	POS is relative to our parent (parent space)
	//
	rat.ui.Element.prototype.handleMouseDown = function (pos, ratEvent)
	{
		if (!this.isVisible())	//	don't handle mouse if I'm invisible - what do they think they're clicking on?
			return false;

		var myBounds = this.getBounds(this.tempRect);
		this.adjustBoundsForPointer(myBounds, ratEvent);
		var inBounds = rat.collision2D.pointInRect(pos, myBounds);

		var handled = false;

		//	OK, but if we have real clipping turned on, and it's outside our space, then don't pass down.
		//	The logic is this:  If we have real clipping turned on, our bounds have to be accurate,
		//	so it's OK to pay attention to that,
		//	and the user can't see anything outside those bounds anyway, since it's clipped during draw,
		//	So, only pass down clicks if we're in bounds or not clipped.
		//	NOTE:  we should probably just require this all the time and stop babying containers that have been set up wrong...
		if (inBounds || (this.flags & rat.ui.Element.clipFlag) === 0)
			handled = this.callForSubElementsWithPos(rat.ui.Element.prototype.handleMouseDown, pos, ratEvent);

		if (inBounds && (!handled || (this.flags & rat.ui.Element.handleAllMouseInMe)))
		{
			//	Convert to local coordinates
			//	Ignore scroll for myself - this position is in "my" space, but not my scrolled content space - worry about that below
			var localPos = this.parentToLocalPos(pos.x, pos.y);
			handled = this.mouseDown(localPos, ratEvent);
		}
		//else
		//	return;		//	if mousedown is not in my space, don't pass down...  this requires containers to be sized correctly!
		//	STT disabling this now - it's too easy for containers to not be right, and confusing to debug.
		//	maybe reenable later

		return handled;
	};

	//	handle mouse up
	//	pos is in parent space
	rat.ui.Element.prototype.handleMouseUp = function (pos, ratEvent)
	{
		//	don't handle mouse if I'm invisible - what do they think they're clicking on?
		if (!this.isVisible || !this.isVisible())	
			return false;
		//rat.console.log("HMU " + ratEvent.eventID + "(" + this.id + " | " + this.name + "): " + pos.x + "," + pos.y);

		//	always call mouseup, even if it's not in our bounds, so we can stop tracking if we were...

		//	Convert to local coordinates
		//	Ignore scroll for myself - this position is in "my" space, but not my scrolled content space - worry about that below
		var localPos = this.parentToLocalPos(pos.x, pos.y);
		//	see if I handled it myself before passing to children.
		var handled = this.mouseUp(localPos, ratEvent);

		if( !handled )
			handled = this.callForSubElementsWithPos(rat.ui.Element.prototype.handleMouseUp, pos, ratEvent);
		return handled;
	};
	
	//
	//	Handle context menu event (right-click)
	//	This is very much like a mouse down, currently.
	//	sorry for the copied and pasted code.  I'm still kinda exploring how this might work...
	//	todo: refactor with mousedown above, and with scrollwheel!
	/*	OLD
	rat.ui.Element.prototype.handleContextMenu = function (pos, ratEvent)
	{
		if (!this.isVisible())	//	don't handle mouse if I'm invisible - what do they think they're clicking on?
			return false;

		var myBounds = this.getBounds(this.tempRect);
		this.adjustBoundsForPointer(myBounds, ratEvent);
		var inBounds = rat.collision2D.pointInRect(pos, myBounds);

		var handled = false;

		//	OK, but if we have real clipping turned on, and it's outside our space, then don't pass down.
		//	The logic is this:  If we have real clipping turned on, our bounds have to be accurate,
		//	so it's OK to pay attention to that,
		//	and the user can't see anything outside those bounds anyway, since it's clipped during draw,
		//	So, only pass down clicks if we're in bounds or not clipped.
		//	NOTE:  we should probably just require this all the time and stop babying containers that have been set up wrong...
		if (inBounds || (this.flags & rat.ui.Element.clipFlag) === 0)
			handled = this.callForSubElementsWithPos(rat.ui.Element.prototype.handleContextMenu, pos, ratEvent);

		if (inBounds && !handled && this.contextMenuEvent)
		{
			//	Convert to local coordinates
			//	Ignore scroll for myself - this position is in "my" space, but not my scrolled content space - worry about that below
			var localPos = this.parentToLocalPos(pos.x, pos.y);
			handled = this.contextMenuEvent(localPos, ratEvent);
		}
		
		return handled;
	};
	*/
	//
	//	Handle context menu (right-click) events, which are standard positional events.
	//	This is still a little copy-pasty to me, but much better than it was.
	rat.ui.Element.prototype.handleContextMenu = function (pos, ratEvent)
	{
		return this.handlePositionalEvent(pos, ratEvent,
		
			//	function to call for my children
			function(pane, pos, ratEvent)
			{
				return pane.handleContextMenu(pos, ratEvent);
			},
			
			//	function to call for any individual pane
			function(pane, localPos, ratEvent)
			{
				if (pane.contextMenuEvent)
					return pane.contextMenuEvent(localPos, ratEvent);
				return false;
			}
		);
	};
	
	//
	//	Handle mouse wheel events, which are standard positional events.
	//
	rat.ui.Element.prototype.handleMouseWheel = function (pos, ratEvent)
	{
		return this.handlePositionalEvent(pos, ratEvent,
		
			//	function to call for my children
			function(pane, pos, ratEvent)
			{
				return pane.handleMouseWheel(pos, ratEvent);
			},
			
			//	function to call for any individual pane
			function(pane, localPos, ratEvent)
			{
				if (pane.mouseWheelEvent)
					return pane.mouseWheelEvent(localPos, ratEvent);
				return false;
			}
		);
	};

	//	Key handling.
	//	Note that dispatch system sends these to target first, and then up parent tree
	//	todo:  document better (explain handleKeyDown vs. keyDown, maybe rename)
	//		a good name for letting people override would be "myKeyDown" or "keydownself" or something, maybe?
	//	todo:  important: come up with a nicer system for externally handling these events, too, like registering/unregistering event handlers,
	//		so multiple outside modules can register for these events and respond to them.
	//		see also things like flagsChanged()
	rat.ui.Element.prototype.handleKeyDown = function (ratEvent)
	{
		if (this.keyDown)
			return this.keyDown(ratEvent);
		return false;
	};
	rat.ui.Element.prototype.handleKeyUp = function (ratEvent)
	{
		if (this.keyUp)
			return this.keyUp(ratEvent);
		return false;
	};
	//	keypress - generally you probably want keydown/keyup, not keypress.
	//	But this is useful for text entry fields..
	rat.ui.Element.prototype.handleKeyPress = function (ratEvent)
	{
		if (this.keyPress)
			return this.keyPress(ratEvent);
		return false;
	};
	
	//	default key down handling for this one element
	//	expected to be overridden by specific implementations
	//	key code is in ratEvent.which
	/*
	rat.ui.Element.prototype.keyDown = function (ratEvent)
	{
		return false;
	};
	rat.ui.Element.prototype.keyUp = function (ratEvent)
	{
		return false;
	};
	*/

	//	moved above
	//	handle mouse wheel event.  event.wheelDelta tells us amount scrolled, where 1 = 1 click up, -2 = 2 clicks down
	//rat.ui.Element.prototype.handleMouseWheel = function (pos, ratEvent)
	//{
	//	return false;
	//};

	//	controller button down
	rat.ui.Element.prototype.handleButtonDown = function (ratEvent)
	{
		return false;
	};
	
	//	controller button up
	rat.ui.Element.prototype.handleButtonUp = function (ratEvent)
	{
		return false;
	};

	//	Handle a dispatched event from the system.
	//
	//	See rat input module, and screen manager's dispatchEvent function.
	//
	//	This is the first place any ui event of any kind is handled.
	//	We break down event types here and call special subfunctions depending on the type.
	//	Any subclass can override this for very special handling if they want,
	//	but generally they just implement a more specific event function like keyDown.
	rat.ui.Element.prototype.handleEvent = function (ratEvent)
	{
		//	Here is where we split out some specific event types.
		//	We didn't have to do it this way.  Everyone could have just overridden "handlEvent",
		//	but this made it easy for games to just override the behavior they wanted without having to override it all.
		var result = false;
		if (ratEvent.eventType === 'keydown')
			result = this.handleKeyDown(ratEvent);
		else if (ratEvent.eventType === 'keyup')
			result = this.handleKeyUp(ratEvent);
		else if (ratEvent.eventType === 'keypress')
			result = this.handleKeyPress(ratEvent);
		
		//	positional events:
		//	We pass "pos" and event separately here,
		//	so that each function can modify pos locally and pass it recursively, without modifying the original rat event.
		//	(sure, we initially pass a position reference, but these handle functions call recursively with new positions.)
		
		//	I really wish we had done ratEvent as the first arg here, but I think I'd break stuff to switch them now.
		
		if (ratEvent.eventType === 'mousedown')
			result = this.handleMouseDown(ratEvent.pos, ratEvent);
		if (ratEvent.eventType === 'mouseup')
			result = this.handleMouseUp(ratEvent.pos, ratEvent);
		
		rat.ui.mouseMoveCallCount = 0;
		if (ratEvent.eventType === 'mousemove')
			result = this.handleMouseMove(ratEvent.pos, false, ratEvent);

		if (ratEvent.eventType === 'contextmenu')	//	right-click
			result = this.handleContextMenu(ratEvent.pos, ratEvent);
		
		if (ratEvent.eventType === 'mousewheel')
			result = this.handleMouseWheel(ratEvent.pos, ratEvent);

		//	controller buttons
		if (ratEvent.eventType === 'buttondown')
			result = this.handleButtonDown(ratEvent);
		if (ratEvent.eventType === 'buttonup')
			result = this.handleButtonUp(ratEvent);

		if (ratEvent.eventType === 'ui')
			result = this.handleUIInput(ratEvent);

		if (result)
			return result;

		//	This is also where we're going to handle passing events up the command chain, if there is one.
		//	currently, we assume visual parent is command parent... (this has nothing to do with inheritance)
		if (this.parent)
		{
			result = this.parent.handleEvent(ratEvent);
			if (result)
				return result;
		}

		return result;
	};

	//	default:  do nothing with UI input
	rat.ui.Element.prototype.handleUIInput = function (event)
	{
		return false;
	};
	
	//	try to make me the target of whatever inputmap or target system is relevant
	rat.ui.Element.prototype.targetMe = function (ratEvent)
	{
		if (this.isTargetable() && this.isEnabled() && this.isVisible())
		{
			//	find my first ancestor with an inputmap, or just the top-most ancestor.
			var myTop = this;
			while (myTop.parent && !myTop.inputMap)
				myTop = myTop.parent;
			//	and use that element's setCurrentTarget mechanism, if there is one.
			//	for a screen, this means something like telling the inputmap which element is now targeted,
			//	and untargeting the rest.
			if (myTop.setCurrentTarget)
			{
				myTop.setCurrentTarget(this, ratEvent);
				return true;
			}
		}
		return false;
	};

	//	Focus and Blur functions to interact nicely with inputMap system
	//	This is called by the inputmap system, or other system to indicate we're selected.
	//	If you want to target an item, call "targetMe()" function.
	rat.ui.Element.prototype.focus = function ()
	{
		if (this.isTargetable() && this.isEnabled() && this.isVisible())
		{
			var wasHighlighted = this.isHighlighted();
			this.setHighlighted(true);
			if (!wasHighlighted && this.events.onFocus)
				this.events.onFocus(this);
			//	note: this generally sucks, since ui elements get focused a lot,
			//	e.g. on opening a screen, coming back from being tabbed out, custom focus logic, etc.
			//	so it plays a lot when we don't want it.
			//	So, I've disabled "focusSound" code.
			//	Maybe look at inputmap "navigateSound" instead.
			//if (!wasHighlighted && this.focusSound && rat.audio)
			//	rat.audio.playSound(this.focusSound);
			this.setTargeted(true);
			return true;
		} else
		{
			return false;
		}
	};

	//	This is called by the inputmap system, or other system to indicate we're not selected.
	rat.ui.Element.prototype.blur = function ()
	{
		if (this.isTargetable() && this.isEnabled() && this.isVisible())
		{
			var wasHighlighted = this.isHighlighted();
			this.setHighlighted(false);

			if( wasHighlighted && this.events.onBlur )
				this.events.onBlur(this);
			this.setTargeted(false);
		}
		return true;
	};

	//	this or another function needs to simulate a click visually, e.g. set a timer and show pushed for a few frames.
	rat.ui.Element.prototype.press = function ()
	{
		if (this.trigger())
			rat.eventMap.fireEvent("uiTriggered", this);
	};

	//
	//	Trigger this element.  e.g. if this is a button, act like it got clicked, and send messages or whatever.
	//	This is implemented at the Element level on purpose - maybe you want a sprite or something to trigger - that's fine.
	//
	//	See notes on SetCallback
	//
	rat.ui.Element.prototype.trigger = function ()
	{
		var telem = rat.telemetry;
		var handled = false;
		if (this.command !== 0)
		{
			if (telem && this.name)
			{
				telem.recordUI('UI com', this.name);
			}

			//console.log("trigger " + this.name + " -> " + this.command);
			if (rat.dispatchCommand(this.command, this.commandInfo))
				handled = true;
		}
		if (this.callback)
		{
			if (telem && this.name)
			{
				telem.recordUI('UI cb', this.name);
			}

			var self = this;
			//	callback can specify if this event was handled or not.
			//	but let's interpret an undefined return value as meaning the callback
			//	doesn't really understand what to return, so in that case let's assume just having a callback
			//	was enough to handle this event.
			//	If you want this event to continue being processed, then explicitly return false.
			var res = this.callback(self, this.callbackInfo)	
			if (res || res === void 0)
				handled = true;
		}
		
		if (handled) {
			//	"clicksound" is a sound to play when this item is triggered.
			if (this.clickSound && rat.audio)
				rat.audio.playSound(this.clickSound);
		}
		
		return handled;
	};

	//	set command to dispatch when triggered
	rat.ui.Element.prototype.setCommand = function (command, commandInfo)
	{
		this.command = command;
		this.commandInfo = commandInfo;
	};

	/**
	 * set function to call when triggered (see Trigger code)
	 * callback is called with (element, userInfo) args
	 * callback is expected to return a flag indicating if the event was handled.
	 * if you return false, we keep looking for other ways the event can be handled.
	 * so, generally you probably want to return true.
	 *
	 * @param {function(?, ?)} callback
	 * @param {*=} userInfo
	 */
	rat.ui.Element.prototype.setCallback = function (callback, userInfo)
	{
		this.callback = callback;
		if (typeof(userInfo) !== 'undefined')
			this.callbackInfo = userInfo;
	};
	
	/**
	 * set function to call when flags change, e.g. when element is highlighted
	 * callback is called with (oldflags, userInfo) args (and using element as 'this')
	 * @param {function(?, ?)} callback
	 * @param {*=} userInfo
	 */
	rat.ui.Element.prototype.setFlagsChangedCallback = function (callback, userInfo)
	{
		this.flagsChangedCallback = callback;
		if (typeof(userInfo) !== 'undefined')
			this.callbackInfo = userInfo;
	};

	/**
	 * Set the data provided with the callbacks
	 * @param {?} userInfo
	 */
	rat.ui.Element.prototype.setCallbackInfo = function (userInfo)
	{
		this.callbackInfo = userInfo;
	};

	/**
	*	Set (or clear) a flag in this element's flags structure
	*	works for setting multiple flags, too.
	*/
	rat.ui.Element.prototype.setFlag = function (flag, val)
	{
		var oldFlags = this.flags;
		
		if (typeof val === 'undefined')
			val = true;
		if (val)
			this.flags |= flag;
		else
			this.flags &= ~flag;
			
		this.checkFlagsChanged(oldFlags);
	};
	
	//	another name for setFlag(flag, false)
	rat.ui.Element.prototype.clearFlag = function(flag)
	{
		this.setFlag(flag, false);
	};
	
	rat.ui.Element.prototype.setVisible = function (visible)
	{
		this.setFlag(rat.ui.Element.visibleFlag, visible);
	};
	rat.ui.Element.prototype.isVisible = function ()
	{
		return ((this.flags & rat.ui.Element.visibleFlag) !== 0);
	};

	rat.ui.Element.prototype.setHighlighted = function (highlighted)
	{
		this.setFlag(rat.ui.Element.highlightedFlag, highlighted);
	};

	rat.ui.Element.prototype.isHighlighted = function (highlighted)
	{
		return ((this.flags & rat.ui.Element.highlightedFlag) !== 0);
	};

	rat.ui.Element.prototype.setEnabled = function (enabled)
	{
		var oldFlags = this.flags;
		if (typeof enabled === 'undefined')
			enabled = true;
		if (enabled)
		{
			this.flags |= rat.ui.Element.enabledFlag;
		} else
		{
			this.flags &= ~rat.ui.Element.enabledFlag;

			//	also clear other flags, in case we were in the middle of something?
			this.flags &= ~rat.ui.Element.highlightedFlag;
			this.flags &= ~rat.ui.Element.pressedFlag;
			//this.flags &= ~rat.ui.Element.toggledFlag;
			this.flags &= ~rat.ui.Element.mouseInFlag;
			//console.log("SET ENABLED FALSE");
			this.flags &= ~rat.ui.Element.trackingMouseDownFlag;
		}
		this.checkFlagsChanged(oldFlags);
	};
	rat.ui.Element.prototype.isEnabled = function ()
	{
		return ((this.flags & rat.ui.Element.enabledFlag) !== 0);
	};

	rat.ui.Element.prototype.setToggled = function (toggled)
	{
		this.setFlag(rat.ui.Element.toggledFlag, toggled);
	};

	rat.ui.Element.prototype.isToggled = function ()
	{
		return ((this.flags & rat.ui.Element.toggledFlag) !== 0);
	};

	rat.ui.Element.prototype.setClipped = function (toClip)
	{
		this.setFlag(rat.ui.Element.clipFlag, toClip);
	};
	rat.ui.Element.prototype.setClip = rat.ui.Element.prototype.setClipped;
	rat.ui.Element.prototype.isClipped = function (toClip)
	{
		return ((this.flags & rat.ui.Element.clipFlag) !== 0);
	};

	rat.ui.Element.prototype.isPressed = function ()
	{
		//	pressed state happens if we were tracking mousedown
		return ((this.flags & rat.ui.Element.pressedFlag) !== 0);
	};
	
	rat.ui.Element.prototype.setTracksMouse = function (tracks)
	{
		this.setFlag(rat.ui.Element.tracksMouseFlag, tracks);
	};
	
	rat.ui.Element.prototype.isTargetable = function ()
	{
		return ((this.flags & rat.ui.Element.targetableFlag) !== 0);
	};
	rat.ui.Element.prototype.setTargetable = function (targetable)
	{
		this.setFlag(rat.ui.Element.targetableFlag, targetable);
	};
	rat.ui.Element.prototype.canBeTarget = rat.ui.Element.prototype.isTargetable;	//	old name
	
	rat.ui.Element.prototype.isTargeted = function ()
	{
		return ((this.flags & rat.ui.Element.targetedFlag) !== 0);
	};
	rat.ui.Element.prototype.setTargeted = function (targeted)
	{
		this.setFlag(rat.ui.Element.targetedFlag, targeted);
	};

	rat.ui.Element.prototype.setAdjustForScale = function (adjust)
	{
		this.setFlag(rat.ui.Element.adjustForScaleFlag, adjust);
	};
	
	//	Scroll a relative amount...  negative dx means content is moved to left (view scrolled to right)
	rat.ui.Element.prototype.scroll = function (dx, dy)
	{
		//console.log("scroll " + dx + ", " + dy);	
		this.contentOffset.x += dx;
		this.contentOffset.y += dy;
		
		//	todo: see if there was actually a change
		if (this.viewChanged)
			this.viewChanged();
		
		//	TODO:  Deal with content offset changing in offscreen rendering, at some point?
		//	at the very least, this needs to set my dirty flag, right?  Here and anywhere we change contentOffset.
	};
	
	//	directly set content offset (like scroll above, but absolute)
	rat.ui.Element.prototype.setContentOffset = function (x, y)
	{
		if( x !== void 0 )
			this.contentOffset.x = x;
		if( y !== void 0 )
			this.contentOffset.y = y;
		
		//	todo: see if there was actually a change
		if (this.viewChanged)
			this.viewChanged();
	};

	//	get current content offset (scroll) value
	rat.ui.Element.prototype.getContentOffset = function ()
	{
		return this.contentOffset.copy();
	};
	//	alternate name
	rat.ui.Element.prototype.getScroll = rat.ui.Element.prototype.getContentOffset;
	
	//	like above, get content offset, but factor in any animation also happening.
	//	return the content offset we expect to reach.
	rat.ui.Element.prototype.getTargetContentOffset = function()
	{
		var list = rat.ui.getAnimatorsForElement(this, rat.ui.Animator.scroller);
		//	if there's more than one, that's basically a bug, but don't worry about it...
		if (list && list.length > 0)
		{
			return list[0].endValue;
		}
		//	no animator - just return offset
		return this.contentOffset.copy();
	};
	
	//	return true if this point (in local space) is in our view space, factoring in scroll values and bounds
	//	useful for scrolled content
	//	(see scrollToShow below for something similar)
	rat.ui.Element.prototype.pointIsInView = function (pos, xSpace, ySpace)
	{
		if (!xSpace)
			xSpace = 0;
		if (!ySpace)
			ySpace = 0;
		var offset = this.contentOffset;
		var scale = this.contentScale;
		if ((pos.x - xSpace)*scale.x + offset.x < 0
				|| (pos.y - ySpace)*scale.y + offset.y < 0)
			return false;
		if ((pos.x + xSpace)*scale.x + offset.x > this.size.x
				|| (pos.y + ySpace)*scale.y + offset.y > this.size.y)
			return false;
		
		return true;
	};

	//	scroll from current position just enough to show this point plus space around it.
	//	"offset" is optional - if it's passed in, set THAT vector, instead of live offset.
	//	"pos" is a center point, and ySpace and xSpace determine how much space to make on each side (like a radius)
	//	see animateScrollToShowElement for a convenient way to scroll to show a subelement
	rat.ui.Element.prototype.scrollToShow = function (pos, xSpace, ySpace, offset)
	{
		if (!offset)
			offset = this.contentOffset;	//	ref
		var scale = this.contentScale;

		//	what values would barely be showing that point?  Make sure we're at least that far.

		var rightEdge = this.size.x - (pos.x + xSpace) * scale.x;		//	x scroll that would bring right edge of object in view
		var leftEdge = -(pos.x - xSpace) * scale.x;	//	x scroll that would bring left edge of object in view
		if (offset.x > rightEdge)
			offset.x = rightEdge;
		else if (offset.x < leftEdge)
			offset.x = leftEdge;

		var bottomEdge = this.size.y - (pos.y + ySpace) * scale.y;
		var topEdge = -(pos.y - ySpace) * scale.y;
		if (offset.y > bottomEdge)
			offset.y = bottomEdge;
		else if (offset.y < topEdge)
			offset.y = topEdge;

		//	if the view has useful content size info, clamp our scroll to not go outside content.
		//	We do want this here, especially when extra space parameters are set.
		this.clampScroll(offset);
	};

	//
	//	animated version of the above - set up an animator to scroll us, over time, to the appropriate position.
	//	TODO:  Maybe remove all these paired functions and just don't animate if time is 0 or undefined.
	//
	rat.ui.Element.prototype.animateScrollToShow = function (pos, xSpace, ySpace, time)
	{
		var offset = this.contentOffset.copy();
		this.scrollToShow(pos, xSpace, ySpace, offset);	//	calculate desired scroll position

		this.animateScroll(offset, time);
	};

	//
	//	Maybe more convenient - scroll to show a specific element
	//
	rat.ui.Element.prototype.animateScrollToShowElement = function (element, extraXSpace, extraYSpace, time)
	{
		if (typeof (extraXSpace) === 'undefined')
			extraXSpace = 0;
		if (typeof (extraYSpace) === 'undefined')
			extraYSpace = 0;
		if (typeof (time) === 'undefined')
			time = 0;

		var bounds = element.getBounds(this.tempRect);
		
		this.animateScrollToShow(
				{ x: bounds.x + bounds.w / 2, y: bounds.y + bounds.h / 2 },
				bounds.w / 2 + extraXSpace,
				bounds.h / 2 + extraYSpace,
				time);
	};

	/**
	* scroll this point to center
	* @param {Object=} offset
	*/
	rat.ui.Element.prototype.scrollToCenter = function (pos, offset)
	{
		if (!offset)
			offset = this.contentOffset;	//	ref
		offset.x = this.size.x / 2 - pos.x * this.contentScale.x;
		offset.y = this.size.y / 2 - pos.y * this.contentScale.y;

		this.clampScroll(offset);
		
		//	todo: see if there was actually a change
		if (this.viewChanged)
			this.viewChanged();
	};

	rat.ui.Element.prototype.animateScrollToCenter = function (pos, time)
	{
		var offset = this.contentOffset.copy();
		this.scrollToCenter(pos, offset);	//	calculate desired scroll position

		this.animateScroll(offset, time);
	};
	
	/**
	* Scroll to center my content automatically
	* @param {Object=} offset
	*/
	rat.ui.Element.prototype.scrollToCenterContent = function (offset)
	{
		if (!offset)
			offset = this.contentOffset;	//	ref
		offset.x = this.size.x / 2 - this.contentSize.x / 2 * this.contentScale.x;
		offset.y = this.size.y / 2 - this.contentSize.y / 2 * this.contentScale.y;

		//	in this case, don't clamp, since half the point is to center content that isn't as large as the view containing it.
	};

	//	animate to a given absolute content offset
	//	(used by other functions above, and directly by some games)
	rat.ui.Element.prototype.animateScroll = function (offset, time)
	{
		//	It seems very unlikely that you'd want to have two scroll animations
		//	active at once.  If this later turns out to be not true,
		//	we can make it optional somehow, or remove this call and depend on caller to decide.
		var hadScroller = rat.ui.killAnimatorsForElement(this, rat.ui.Animator.scroller);
		
		var animator = new rat.ui.Animator(rat.ui.Animator.scroller, this);
		animator.setTimer(time);
		//	todo, scale this by distance, somehow?  pass in pixels per sec speed instead of time?
		var startVal = { x: this.contentOffset.x, y: this.contentOffset.y };
		var endVal = { x: offset.x, y: offset.y };
		animator.setStartEndVectors(startVal, endVal);
		animator.setAutoDie();	//	kill animator when it's done
		
		//	if we were already scrolling, don't bother with ramp up in movement...
		//	this helps is avoid something like:  user holds down scroll key,
		//	and because keep easing in each frame, we only move a tiny bit until they let go.
		if (hadScroller)
			animator.setInterpFilter(rat.ui.Animator.filterEaseOut);
	};

	/**
	//	clamp scroll offset to keep from scrolling past edges of actual content,
	//	based on contentSize being correct.
	//	todo allow optional passing in a potential value, and clamp that instead of my current value.
	* @param {Object=} offset
	*/
	rat.ui.Element.prototype.clampScroll = function (offset)
	{
		if (!offset)	//	if one wasn't passed in, use the live one (set a ref to it)
			offset = this.contentOffset;

		if (this.contentSize.x <= 0 || this.contentSize.y <= 0)
			return;	//	was never set...

		var leftMax = -(this.contentSize.x * this.contentScale.x - this.size.x);	//	the farthest contentoffset.x can go
		var upMax = -(this.contentSize.y * this.contentScale.y - this.size.y);	//	the farthest contentoffset.y can go

		if (offset.x < leftMax)
			offset.x = leftMax;
		if (offset.y < upMax)
			offset.y = upMax;
		if (offset.x > 0)
			offset.x = 0;
		if (offset.y > 0)
			offset.y = 0;
	};
	
	//	Here's how we're going to implement zoom.
	//	Like contentSize and offset, the zoom of a pane will refer to the *contents*
	//		of that pane being zoomed by that value.
	//	Why?  We want to be able to zoom the contents of a scrollview, without
	//		having to make assumptions about the content, like knowing there's only one element.
	//		So, easiest to say "zoom refers to my contents".
	//		Our contents will be oblivious to being zoomed, and our contentSize will still refer
	//		to the natural size of our contents before being scaled.
	//	We're going to use a new "zoom" value instead of the existing scale value.
	//	Why?  Because scale already seems to refer to ME being scaled, including my frame?
	//		and it will correlate better with contentSize,
	//		and it'll be easier to insert in the right place in the draw logic, which is complicated.
	//		currently, scale happens pretty early, as part of the whole frame transform...
	//	Like offset, zoom will be implemented at this Element level rather than in scrollview.
	//	Why?  Because it will let us zoom things without having to make them scrollviews,
	//		and when we need to factor zoom into things like localtoglobal position calculation,
	//		we can do that in the base level functions here without having to override elsewhere.
	//		We also will want to do some nice simultaneous zoom/scroll handling, so let's do it
	//		in the same place.
	
	//	directly set content scale
	rat.ui.Element.prototype.setContentScale = function (x, y)
	{
		if( x !== void 0 )
			this.contentScale.x = x;
		if( y !== void 0 )
			this.contentScale.y = y;
		this.setDirty(true);
		
		//	todo: see if there was actually a change
		if (this.viewChanged)
			this.viewChanged();
	};
	//	add a value to current zoom/scale level
	//		(add instead of multiply so that it can be undone)
	rat.ui.Element.prototype.stepZoom = function(delta)
	{
		this.contentScale.x += delta;
		this.contentScale.y += delta;
		this.clampContentScale();
		this.setDirty(true);
		
		//	todo: see if there was actually a change
		if (this.viewChanged)
			this.viewChanged();
	};
	//	clamp scale to the min/max we set previously.
	rat.ui.Element.prototype.clampContentScale = function()
	{
		//	hey, while we're at it, fix weird rounding problems like getting a scale of 1.2000000000002
		
		function trunc(x) { return (((x+0.001) * 100)|0)/100;}
		
		this.contentScale.x = trunc(this.contentScale.x);
		this.contentScale.y = trunc(this.contentScale.y);
		
		if (this.contentScale.x < this.contentScaleMin.x)
			this.contentScale.x = this.contentScaleMin.x;
		if (this.contentScale.y < this.contentScaleMin.y)
			this.contentScale.y = this.contentScaleMin.y;
		if (this.contentScale.x > this.contentScaleMax.x)
			this.contentScale.x = this.contentScaleMax.x;
		if (this.contentScale.y > this.contentScaleMax.y)
			this.contentScale.y = this.contentScaleMax.y;
	};
	rat.ui.Element.prototype.setContentScaleLimits = function(min, max)
	{
		this.contentScaleMin.x = this.contentScaleMin.y = min;
		this.contentScaleMax.x = this.contentScaleMax.y = max;
	};
	
	//	I deeply question this function.  See notes in Sprite module.
	rat.ui.Element.prototype.drawTiled = function(w, h)
	{
		//	nothing, usually overridden
	};

	//	standard draw function does all the work of putting us in the right space,
	//	drawing subElements, etc.
	//	calls drawSelf() for easy overriding in subclasses
	//	(generally speaking, nobody else will need to override draw().  They can just use drawSelf)
	rat.ui.Element.prototype.draw = function (toOffscreen)
	{
		if (!this.isVisible())	//	don't draw me or sub stuff if I'm invisible
			return;
		if (toOffscreen === void 0)	//	not specified
			toOffscreen = false;
		
		//	Give panes a chance to update our state before we draw
		//	note that we're passing in offscreen flag now, so we'll let preDraw decide if it should respect offscreen flag.
		//if (!toOffscreen && this.preDraw )
		if (this.preDraw)
		{
			//	do this BEFORE we handle dirty pane rendering
			res = this.preDraw(toOffscreen);
			if (res && res.abortDraw)
				return;
		}
		
		//	If we're supposed to be using an offscreen buffer, and it doesn't exist or needs updating, update it now.
		//	this will re-enter this function with toOffscreen set to true, among other things.  :)
		if (this.useOffscreen &&
			(!this.offscreen
			|| this.isDirty
			|| (this.checkDirty && this.checkDirty())
			))
		{
			this.renderOffscreen();
			//	and then continue on below, with a prepared offscreen!
		}

		var ctx = rat.graphics.getContext();
		
		//	Rendering to offscreen?  If so, skip a whole bunch of stuff.
		
		//	if rendering offscreen, don't do save/restore or transforms.
		//	Just render cleanly into correctly-sized offscreen buffer.
		//	This is important - we don't want offscreen buffer size affected by position or rotation.
		
		if (!toOffscreen)
		{
			rat.graphics.save();

			// Use applyTransformation() function if one is present,
			// so subclasses can change transformation behavior, if needed.
			// (i.e. change transformation order, change how centering works, etc.)
			if (this.applyTransformation) {
				this.applyTransformation(ctx);
			}
			else {
				rat.graphics.translate(this.place.pos.x, this.place.pos.y);
				if (this.place.rot.angle)
					rat.graphics.rotate(this.place.rot.angle);
				if (this.scale.x !== 1 || this.scale.y !== 1)
					rat.graphics.scale(this.scale.x, this.scale.y);			
			}
		
			//	include or don't include frame?
			//	Frame drawing could go inside the offscreen buffer,
			//		but we need to commit to whether the frame is exactly aligned, and inside our space, or outside.
			//		if outside, it can't be in the buffer, which is always our exact size.
			
			rat.graphics.frameStats.totalElementsDrawn++;	//	for debugging, track total elements drawn per frame
			
			if (this.opacity < 1)
				ctx.globalAlpha = this.opacity;
			
		}	//	end of !toOffscreen check
		
		//	if smoothing off, turn off for me and my children.
		//	todo: move to a whole separate "render style" flag set?
		//	todo: support a subelement being smoothed inside that?  That might be weird.
		//	would require another flag, anyway, to track if it had been set...
		var wasSmoothing;
		if (this.flags & rat.ui.Element.unSmoothFlag)
			wasSmoothing = rat.graphics.setImageSmoothing(false);
		
		//	draw my frame, if any.
		//	One problem with drawing the frame here is that it gets scaled,
		//	if there's a scale.  In lots of other ways, we ignore scale when dealing
		//	with bounds, right?  Are frames and bounds supposed to be the same?
		//	We mostly use frames to debug, so it's nice for them to match bounds...
		//	Frames are weird.
		if (this.frameWidth > 0)
		{
			ctx.strokeStyle = this.frameColor.toString();
			ctx.lineWidth = this.frameWidth;
			ctx.strokeRect(-this.center.x - this.frameOutset, -this.center.y - this.frameOutset,
					this.size.x + 2 * this.frameOutset, this.size.y + 2 * this.frameOutset);
		}

		//	Apply clipping, if needed.
		if (!toOffscreen && this.flags & rat.ui.Element.clipFlag)
		{
			ctx.beginPath();
			ctx.rect(0, 0, this.size.x, this.size.y);
			ctx.clip();
		}
		
		//	render FROM my offscreen image.
		if (!toOffscreen && this.useOffscreen && this.offscreen)
		{
			//	center support here only works this cleanly because we were careful to skip centering when we rendered in renderOffscreen()
			this.offscreen.render(ctx, -this.center.x, -this.center.y);
			
			//	TODO:  If rendering FROM offscreen, and transform is simple, skip the whole save/restore business above as well.
			
		} else {	//	finally, a normal manual draw
		
			//	TODO: What's with all the inconsistent arguments here?
			//	sometimes we pass "this", maybe in case the function pointer isn't set up to get a "this"?
			//	(in which case we should maybe be calling using "call" instead?)
			//	and sometimes we pass ctx, which I wish we were *always* passing to every draw function.
			
			if (this.events.beforeDraw)
				this.events.beforeDraw(this);

			if (this.drawSelfPre)
				this.drawSelfPre(this);
			
			//	drawSelf is for self and background, not to be scrolled like subpanes below.
			this.drawSelf(ctx);

			if (this.events.onDrawSelf)
				this.events.onDrawSelf(this);

			//	get ready to draw sub elements IF we have any...
			if (this.subElements)
			{
				//	scroll my sub-element content,
				//	and let's factor in centering now, too.
				var offsetX = this.contentOffset.x + -this.center.x;
				var offsetY = this.contentOffset.y + -this.center.y;
				
				//if( this.contentOffset.x !== 0 || this.contentOffset.y !== 0 )
				//	rat.graphics.translate(this.contentOffset.x, this.contentOffset.y);
				if (offsetX !== 0 || offsetY !== 0)
					rat.graphics.translate(offsetX, offsetY);
				
				//	scale my content
				if( this.contentScale.x !== 1 || this.contentScale.y !== 1 )
					rat.graphics.scale(this.contentScale.x, this.contentScale.y);
				
				if (this.events.onPreDrawChildren)
					this.events.onPreDrawChildren(this);
				
				this.drawSubElements();

				if (this.events.onDrawChildren)
					this.events.onDrawChildren(this);
				
				//	untranslate and unscale, in case drawSelfPost and afterDraw need to do something.
				//	Those are expected to operate in the same space as drawSelf!
				if( this.contentScale.x !== 1 || this.contentScale.y !== 1 )
					rat.graphics.scale(1/this.contentScale.x, 1/this.contentScale.y);
				if (offsetX !== 0 || offsetY !== 0)
					rat.graphics.translate(-offsetX, -offsetY);
			}
			
			if (this.drawSelfPost)
				this.drawSelfPost(this);

			if (this.events.afterDraw)
				this.events.afterDraw(this);
		}
		
		//	did we unsmooth?  If so, restore old value now.
		if (this.flags & rat.ui.Element.unSmoothFlag)
			rat.graphics.setImageSmoothing(wasSmoothing);
		
		//	Give panes a chance to clean up from preDraw.
		if (this.postDraw)
		{
			this.postDraw(toOffscreen);
		}

		if (!toOffscreen)
			rat.graphics.restore();
	};

	//
	//	Draw self.  Usually overridden.  This is called before our subpanes are drawn.
	//
	rat.ui.Element.prototype.drawSelf = function (ctx)
	{
		//	nothing, usually overridden
	};

	//	draw all subelements
	//	Current context is set up to my local coordinates (including scrolled content offset, if any)
	rat.ui.Element.prototype.drawSubElements = function ()
	{
		if (this.subElements)
		{

			//	our bounds don't change when we're scrolled, but our content does, and we need to factor that in when checking visibility below.
			//	instead of adding offset to every subbounds below, let's just adjust the bounds we check here, once.
			var checkBounds = this.tempRect;
			checkBounds.x = -this.contentOffset.x;
			checkBounds.y = -this.contentOffset.y;
			checkBounds.w = this.size.x / this.contentScale.x;
			checkBounds.h = this.size.y / this.contentScale.y;

			for (var i = 0; i < this.subElements.length; i++)
			{
				var sub = this.subElements[i];
				
				//	an idea for minor performance improvements - skip a few function calls by immediately testing
				//	sub.flags & rat.ui.Element.visibleFlag
				//	here?  Probably not worth it?  Let's try anyway.
				if (!(sub.flags & rat.ui.Element.visibleFlag))
					continue;

				//	If we have clipping turned on, and this item is outside our bounds, then don't draw it.
				//	if there's a rotation, give up - maybe it's rotated partly into view?  todo: real polygon intersect math, in my (I am the parent) space.

				var toDraw = true;
				if (((this.flags & rat.ui.Element.clipFlag) !== 0) && sub.place.rot.angle === 0)
				{
					var subBounds = sub.getBounds(this.tempRect);
					//	factor in scale mathematically so we can do correct overlap check
					/*	Disabling, since getBounds() is factoring in scale now.
					subBounds.x *= sub.scale.x;
					subBounds.y *= sub.scale.y;
					subBounds.w *= sub.scale.x;
					subBounds.h *= sub.scale.y;
					*/
					//	Also, it was wrong to scale x and y by the sub pane's scale!
					//	Those values are in parent space.
					//	We should be scaling them by THIS pane's scale, right?
					//	But since we didn't do it above with checkBounds, don't do it here, either...?
					//	If this ever turns out wrong, be sure to add scale to both calculations (STT 2014.1.21)
					//subBounds.x *= this.scale.x;
					//subBounds.y *= this.scale.y;

					if (!rat.collision2D.rectOverlapsRect(subBounds, checkBounds))
						toDraw = false;
				}
				if (toDraw)
					sub.draw();
			}
		}
	};
	
	//	For convenience, this function will create a colored background object inside this element.
	rat.ui.Element.prototype.setBackground = function (color)
	{
		//	background
		var back = new rat.ui.Shape(rat.ui.squareShape);
		back.setColor(color);
		this.insertSubElement(back);
		back.autoSizeToParent();

		this.elementBackground = back;
		return back;
	};
	//	for convenience, set the element's background frame size and color
	//	not much different from setting the frame for the element, right?  Why is this here?
	rat.ui.Element.prototype.setBackgroundFrame = function (frameWidth, frameColor)
	{
		if (this.elementBackground)
			this.elementBackground.setFrame(frameWidth, frameColor);
	};

	//	base boundsChanged function - called any time position or size changes.
	//	nice for overriding, so various subclasses can react to their pos/size changing.
	//	Remember, though, that any overriding needs to call this inherited function, or do the work it does!
	rat.ui.Element.prototype.boundsChanged = function ()
	{
		//	sometimes overridden, as well, but this function should always get called
		if ( this.toolTip )
			this.positionToolTip( this.toolTipPlacement, this.toolTipPlacementOffset, this.toolTipPlacementFromMouse );
		
		//	TODO: would be nice to know whether it was pos or size.  If pos, don't need to mark dirty!
		this.setDirty(true);
	};
	
	//	base stateChanged function - called any time our main set of flags changes
	//	nice for overriding, so various subclasses can easily react to being highlighted or whatnot,
	//	without having to override every single state handling function and call inherited function in each, etc.
	rat.ui.Element.prototype.flagsChanged = function (oldFlags)
	{
		//console.log("flags changed " + oldFlags + " -> " + this.flags);
	};
	//	check if flags actually did change, and if so, call flagsChanged and registered callbacks
	rat.ui.Element.prototype.checkFlagsChanged = function (oldFlags)
	{
		if (oldFlags !== this.flags)
		{
			//	Whether this changes our look depends on what class we are.
			//	But to make everybody's life easier, we'll do all the work here, based on flagsThatDirtyMe flag.
			//	see comments where that variable is defined above.
			if (((oldFlags ^ this.flags) & this.flagsThatDirtyMe) !== 0)
				this.setDirty(true);
			//	see above, again...  some flags only dirty my parent
			if ((((oldFlags ^ this.flags) & this.flagsThatDirtyParent) !== 0) && this.parent)
				this.parent.setDirty(true, true);
			
			this.flagsChanged(oldFlags);
			//	TODO: more generic system for handling callbacks - register callbacks for any interesting event.  See other notes in this file.
			//	see event system - use that?
			if (this.flagsChangedCallback)
				this.flagsChangedCallback(oldFlags, this.callbackInfo);
		}
	};

	//	Debug utility to put a random-colored frame on ALL elements and subelements
	rat.ui.Element.prototype.frameAllRandom = function ()
	{
		this.applyRecursively(rat.ui.Element.prototype.setFrameRandom, 1);
		
		//	I think frames are outside offscreens.
		//	so, no dirty.
		//this.setDirty(true);
	};

	/**  Fire a trigger on this element.
	   	Compare and contrast with flagsChangedCallback, which is a more generic superset of this idea.
 */
	var customTriggerPhases = ['before_', 'on_', 'after_'];
	/**
	 * @param {string} triggerName
	 * @param {?} triggerArgs
	 */
	rat.ui.Element.prototype.fireCustomTrigger = function (triggerName, triggerArgs)
	{
		var funcName;
		for( var index = 0; index !== customTriggerPhases.length; ++index )
		{
			funcName = customTriggerPhases[index] + triggerName;
			if( this[funcName] )
			{
				var continueTrigger = this[funcName](this, triggerName, triggerArgs);
				if (continueTrigger !== void 0 && !continueTrigger)
					return;
			}
		}
	};
	
	//	set whether we want to use offscreen rendering for this element
	rat.ui.Element.prototype.setUseOffscreen = function (useOff)
	{
		if (!rat.ui.allowOffscreens)	//	global disable of ui offscreens.  See above.
			return;
		
		if (this.useOffscreen !== useOff)
			this.setDirty(true);
		this.useOffscreen = useOff;
	};
	
	//	dirty tracking, so we know when to rebuild offscreen
	//	because any change to me affects the look of my parents, set them dirty as well.
	//	except... if I'm invisible, I don't think I should be marking anyone dirty...
	//	we were having trouble with invisible things (or things inside invisible things)
	//	changing in some way and making somebody way up their chain rebuild their offscreen even though it wasn't needed.
	//	So, now I'm skipping this whole call if I'm invisible.
	//	This means that actual visibility changes need to set dirty before the object is invisible!
	//	see checkFlagsChanged and second argument here, which means force the call
	rat.ui.Element.prototype.setDirty = function (isDirty, force)
	{
		if (!this.isVisible() && !force)
			return;
		
		if (isDirty === void 0)
			isDirty = true;
		this.isDirty = isDirty;
		
		//	temp debug code.
		//if (this.useOffscreen && isDirty && this.id === 'player1')
		//{
		//	rat.console.logOnce("heyp1");
		//}
		
		//	this also means my parent is dirty.
		if (isDirty && this.parent)
			this.parent.setDirty(true);
	};
	
	//	This is a little unusual - normally, when one element is dirty, it just needs its parents to know.
	//	see setDirty above.
	//	But in some systems (e.g. XUI), when one thing changes in a certain way (e.g. opacity for a group)
	//	it means all its children will draw differently...
	//	and if any of them have offscreen rendering, those things need to be rerendered.
	//	so, this function is useful for that.  But don't use it unless you really think you need it.
	rat.ui.Element.prototype.setDirtyRecursive = function (isDirty)
	{
		//	whoah, that sucked!  This is setting each one repeatedly up and down the tree.  Let's  not do that...
		//this.applyRecursively(rat.ui.Element.prototype.setDirty, isDirty);

		this.setDirty(isDirty);
		this.applyRecursively(function mySetDirty(theVal) {
			this.isDirty = theVal;
		}, isDirty);
	};
	
	//	Render offscreen version of this element.
	//	Faster to render a precalculated image than draw every frame.
	rat.ui.Element.prototype.renderOffscreen = function()
	{
		//console.log("renderOffscreen " + this.id);
		
		//console.log("ACTUAL RENDER OFFSCREEN for " + this.name);
		//if (this.elementType === 'textBox')
		//	console.log(this.value);
		//	TODO: more optimal width/height usage, based on contentsize?
		//		would be tricky, with offsets and centering and stuff.
		
		var width = this.size.x;
		var height = this.size.y;
		
		var off;
		if (this.offscreen)	//	already have one, just make sure it's the right size
		{
			off = this.offscreen;
			off.setSize(width, height, true);
		} else	//	need a new one
			off = new rat.Offscreen(width, height);
		
		var ctx = off.getContext();
		
		var oldCtx = rat.graphics.getContext();	//	remember old context
		rat.graphics.setContext(ctx);
		this.useOffscreen = false;	//	force drawSelf to do it the normal way this time
		
		//	TODO:  This ignoreCenter flag was from text drawing.  Figure out how to make this more generic!
		//		at the very least, use toOffscreen flag being passed to draw()?
		//		would have to pass to drawSelf, too...
		//		why is centering not handled  automatically in draw(), anyway?
		//		Why is it in subclasses, like text?
		//		IT SHOULD BE handled in draw.  This is a mistake.  Fix it.
		//		drawSelf for each subclass should not have to factor in this.center.x
		//		like they all do.  :(  But I don't want to break everything right now...  Fix later.
		this.ignoreCenter = true;	//	see alignment calculations
		
		this.draw(true);
		
		if (rat.ui.debugOffscreens)
			off.applyDebugOverlay();
		
		this.ignoreCenter = false;
		
		this.useOffscreen = true;
		rat.graphics.setContext(oldCtx);	//	restore old context
		
		this.offscreen = off;
		
		this.isDirty = false;
		//	and we have now drawn all subelements, too, so set them not dirty, either.
		this.applyRecursively(function mySetDirty(arg){this.isDirty = false;});
	};

	// Support for creation from data
	//id:""
	//frame:{
	//	size:00,
	//	color:{
	//		r:00,
	//		g:00,
	//		b:00,
	//		a:0
	//	}
	//},
	//bounds: {
	//	x: {
	//		percent: true,
	//		centered/centered:true,
	//		fromCenter: true,
	//		fromMyEdge/fromMyFarEdge:true,
	//		fromParentEdge/fromParentFarEdge:true,
	//		val:00
	//	}/x:00,
	//	y: {
	//		percent: true,
	//		centered/centered:true,
	//		fromCenter: true,
	//		fromMyEdge/fromMyFarEdge:true,
	//		fromParentEdge/fromParentFarEdge:true,
	//		val:00
	//	}/y:00,
	//	w:{
	//		fromParent:true,
	//		percent: true,
	//		val:00
	//	}/w:00,
	//	h:{
	//			fromParent:true,
	//			percent: true,
	//			val:00
	//	}/h:00
	//},
	//visible:false,
	//highlighted:true,
	//enabled:false,
	//toggled:true,
	//clip:true,
	//contentOffset:{
	//	x:00,
	//	y:00
	//}
	
	rat.ui.Element.editProperties = [
		{ label: "basic",
			props: [
				{propName:'type', type:'string'},
				{propName:'id', type:'string'},
				{propName:'name', type:'string'},
			],
		},
		{ label: "location",
			props: [
				{label:"pos", propName:'bounds', type:'xyvector', valueType:'float'},
				{label:"size", propName:'bounds', type:'whvector', valueType:'float'},
				//	nobody but scrollview uses this.  So, I'm going to set it there.
				//	Feel free to move this back if you want.
				//{label:"contentSize", propName:'contentSize', type:'sizevector', valueType:'float'},
				{propName:'autoCenter', type:'boolean', tipText:"anchor from center"},
				//	todo: support more explicit anchor values as well!
				{propName:'rotation', type:'float', tipText:"rotation around anchor, in radians"},
			],
		},
		/*	TODO: this is hard to support, and confusing.  :(
		{ label: "autoLocation", defaultClosed:true,
			props: [
				
				{label:"x%", propName:'bounds.x.percent', type:'boolean'},
				{label:"y%", propName:'bounds.y.percent', type:'boolean'},
				{label:"w%", propName:'bounds.w.percent', type:'boolean'},
				{label:"h%", propName:'bounds.h.percent', type:'boolean'},
				{label:"autoFill", propName:'bounds.autoFill', type:'boolean'},
				
				{label:"xVal", propName:'bounds.x.val', type:'float'},
				{label:"yVal", propName:'bounds.y.val', type:'float'},
				{label:"wVal", propName:'bounds.w.val', type:'float'},
				{label:"hVal", propName:'bounds.h.val', type:'float'},
			],
		},
		*/
		{ label: "appearance", defaultClosed:true,
			props: [
				{propName:'color', type:'color', tipText:"content color, used depending on type"},
				{propName:'frameSize', type:'float'},
				{propName:'frameColor', type:'color'},
				{propName:'frameOutset', type:'float', tipText:"expand or shrink frame (in pixels)"},
			],
		},
		
		{ label: "flags", defaultClosed:true,
			props: [
				{propName:'visible', type:'boolean'},
				//	highlight is runtime
				{propName:'enabled', type:'boolean'},
				{propName:'toggled', type:'boolean'},
				{propName:'clip', type:'boolean'},
				{propName:'autoSizeAfterLoad', type:'boolean'},
				{propName:'autoScaleAfterLoad', type:'boolean'},
				{propName:'unSmooth', type:'boolean'},
				{propName:'drawTiled', type:'boolean'},
				{propName:'useOffscreen', type:'boolean'},
			],
		},
	];
	/** @suppress {missingProperties} */
	rat.ui.Element.setupFromData = function (pane, data, parentBounds)
	{
		//	set my bounds.
		pane.setBounds(rat.ui.data.calcBounds(data, parentBounds));
		if (data.rotation !== void 0 || data.rot !== void 0)
			pane.setRotation(data.rotation || data.rot);
		
		//	support setting my center values, which is different from just one-time centering position above,
		//	in cases like animating scale, which really needs to know where my conceptual center is, later.
		if (data.autoCenter !== void 0)
			pane.autoCenter(data.autoCenter);
		//	todo: support more explicit centering, or horiz/vert centering separately?
		
		//	Do i want a frame?
		if (data.frame)	//	here's one way to set it, with a "frame" structure.
			pane.setFrame(data.frame.size, rat.graphics.fixColorStyle(data.frame.color), data.frame.outset);
		//	here's another...
		if (data.frameSize !== void 0)
			pane.setFrame(data.frameSize, rat.graphics.fixColorStyle(data.frameColor), data.frameOutset);

		if( data.id !== void 0 )
			pane.setID( data.id );

		//	States/settings
		if (data.color)
			pane.setColor(new rat.graphics.Color(data.color));
		
		//	Flags
		//	todo: support more flexible "flags" value in addition to (instead of?) custom names for each of these flags?
		//		There's value in calling these functions (e.g. setEnabled()) beyond the actual bit setting.
		//		So, we need to do that either way.  Maybe walk through the flags one at a time, and have a list of functions to call for each?
		//		In the mean time, we require custom flag names for each flag, which just adds more coding work each time we add support
		//		for a new flag.
		if( data.visible !== void 0 )
			pane.setVisible(!!data.visible);
		if( data.highlighted !== void 0 )
			pane.setHighlighted(!!data.highlighted);
		if (data.enabled !== void 0)
			pane.setEnabled(!!data.enabled);
		if (data.toggled !== void 0)
			pane.setToggled(!!data.toggled);
		if (data.clip !== void 0)
			pane.setClip(!!data.clip);
		if (data.autoSizeAfterLoad !== void 0)
			pane.setFlag(rat.ui.Element.autoSizeAfterLoadFlag, !!data.autoSizeAfterLoad);
		if (data.autoScaleAfterLoad !== void 0)
			pane.setFlag(rat.ui.Element.autoScaleAfterLoadFlag, !!data.autoScaleAfterLoad);
		if (data.drawTiled !== void 0)
			pane.setFlag(rat.ui.Element.drawTiledFlag, !!data.drawTiled);
		if (data.unSmooth !== void 0)
			pane.setFlag(rat.ui.Element.unSmoothFlag, !!data.unSmooth);
		
		if (data.contentSize && data.contentSize.x)
			pane.setContentSize(data.contentSize.x, data.contentSize.y);
		if (data.contentOffset)
			pane.setContentOffset(data.contentOffset.x, data.contentOffset.y);
		if (data.contentScale)
			pane.setContentScale(data.contentScale.x, data.contentScale.y);
		//	todo: setContentScaleLimits
			
		if (data.callback)
			pane.setCallback(data.callback, data.callbackInfo);

		if (data.onFocus)
			pane.events.onFocus = data.onFocus;
		if (data.onBlur)
			pane.events.onBlur = data.onBlur;
		if (data.beforeDraw)
			pane.events.beforeDraw = data.beforeDraw;
		if (data.onDrawSelf)
			pane.events.onDrawSelf = data.onDrawSelf;
		if (data.onPreDrawChildren)
			pane.events.onPreDrawChildren = data.onPreDrawChildren;
		if (data.onDrawChildren)
			pane.events.onDrawChildren = data.onDrawChildren;
		if (data.afterDraw)
			pane.events.afterDraw = data.afterDraw;
		
		if (data.useOffscreen !== void 0)
			pane.setUseOffscreen(data.useOffscreen);
	};

	//	old naming convention
	//rat.graphics.Element = rat.ui.Element;
} );
//--------------------------------------------------------------------------------------------------
//
//	Test special ui elements
//	
//	TODO: move to rtest, remove from rat engine.
//

//	build test screen
rat.modules.add( "rat.test.r_test_special_ui",
[
	{ name: "rat.test.r_test", processBefore: true },
	
	"rat.graphics.r_graphics",
	"rat.ui.r_screen",
	"rat.ui.r_screenmanager",
	"rat.ui.r_ui_textbox",
	"rat.ui.r_ui_fillbar",
	"rat.ui.r_ui_spiderchart",
	"rat.ui.r_ui_treeview",
	"rat.ui.r_ui",
],
function(rat)
{
	rat.test.setupSpecialUITest = function ()
	{
		var screenWidth = rat.graphics.SCREEN_WIDTH;
		var screenHeight = rat.graphics.SCREEN_HEIGHT;

		var container = new rat.ui.Screen();
		container.setPos(0, 0);
		container.setSize(screenWidth, screenHeight);

		rat.screenManager.setUIRoot(container);

		var mtbox = new rat.ui.TextBox("Test: Special UI");
		mtbox.setFont("arial bold");
		mtbox.setFontSize(20);
		mtbox.setPos(0, 12);
		mtbox.setSize(screenWidth, 30);
		mtbox.setAlign(rat.ui.TextBox.alignCenter);
		mtbox.setFrame(1, rat.graphics.yellow);
		mtbox.setColor(new rat.graphics.Color(180, 180, 210));
		container.appendSubElement(mtbox);

		//	fillbar
		var fbar = new rat.ui.FillBar();
		fbar.setPos(20, 70);
		fbar.setSize(200, 20);
		fbar.setFill(25, 100);
		//	back, body, frame
		fbar.setColors(rat.graphics.green, rat.graphics.yellow, rat.graphics.red);
		container.appendSubElement(fbar);

		//	spider chart
		var schart = new rat.ui.SpiderChart();
		schart.setPos(20, 100);
		schart.setSize(200, 200);
		schart.setData({
			normalize : true,	//	normalize and fill visual space
			normalizeScale : 0.9,	//	but don't go to full edge - go 90% out

			drawAxes : {
				color: rat.graphics.gray,
				scale: 0.95,	//	just scales total axis lines, not hash marks, which need to use normalizescale above to match data
				hashColor: new rat.graphics.Color(0xA0, 0xA0, 0xA0, 0.3),	//	rat.graphics.lightGray,
				hashCount: 5,
				hashLines: true,	//	draw full lines, or just little marks?
			},

			//	data sets
			sets : [

				{
					fillColor: new rat.graphics.Color(0x80, 0x80, 0xFF, 0.3),
					strokeColor: rat.graphics.blue,
					strokeWidth: 2,
					points: [4, 5, 2, 3, 4, 6],
				},

				/*
				{
					//fillColor: rat.graphics.green,
					strokeColor: rat.graphics.violet,
					strokeWidth: 2,
					points: [3, 6, 3, 3, 4, 5],
				},
				*/

				{
					//fillColor: rat.graphics.yellow,
					fillColor: new rat.graphics.Color(0xFF, 0x80, 0x80, 0.3),
					strokeColor: rat.graphics.red,
					strokeWidth: 2,
					points: [3, 6, 4, 4, 3, 4],
				},


			],
		});

		container.appendSubElement(schart);
		
		//	tree view
		var tview = new rat.ui.TreeView(container);
		tview.setPos(260, 100);
		tview.setSize(200, 200);
		tview.setTreeValues(
			{
				label: "root",
				children:
				[
					{label: '1 - one'},
					{label: '2 - two',
						children: [
							{label: '2a'},
							{label: '2b'},
						],
					},
					{label: '3 - three'},
				],
			}
		);
		
		//	set me up to update...

		rat.test.tests.push({
			update: rat.test.updateSpecialUITest,
			data1: { data: null },
		});

	};

	rat.test.updateSpecialUITest = function (dt, testData)
	{
		//	something?
	};
} );
//--------------------------------------------------------------------------------------------------
//
//	A simple harness for testing a few things?
//	Maybe not very useful, and less useful than real unit tests...
//
rat.modules.add( "rat.test.r_test",
[], 
function(rat)
{
	rat.test = {
		tests: [],

		update : function (dt)
		{
			if (!rat.test.tests)
				return;
			for (var i = 0; i < rat.test.tests.length; i++)
			{
				if (rat.test.tests[i].update)
					rat.test.tests[i].update(dt, rat.test.tests[i]);
			}
		},
	};
} );

//--------------------------------------------------------------------------------------------------
/**

	Rat Audio module

	Features:
		Load sounds ready for playing by id
		Handling of different formats for different browsers/platforms
			Though at this point it looks like mp3 works everywhere we care about.
		Set up sounds in groups and pick random sounds in those groups when requested
		Restart sounds when a new attempt is made to play the same sound
		Master volume.

	Eventual Features / TODO
		Master volume fade in/out?
		Per-sound properties like how far into the sound to get before allowing restarting

		store our variables in a separate structure instead of modifying sound object.

		Consider one of the more standard/supported audio systems already out there?
		We'd still need to build our features (like play random, auto-restart, fade, wraith support, etc.)
			on top of that library.

		Background music track handling:
			independent mute/volume for music
			support automatically looping through a set of tracks?  (not high priority)
			convenient function for transitioning to new track,
				which includes fading current track out automatically

		Bug fixing To do:

			Bug:  If browser supports mp3 and extension is not specified, we try m4a but not mp3.

			Check out melonjs - does his audio work with iOS?  He does retry after error like other solutions,
			but how will that work on iPad?

			How about the trick one guy mentioned where he loads audio without identifying it as audio format,
			so iOS browsers don't screw with it.
		very potentially useful:
			http://stackoverflow.com/questions/3009888/autoplay-audio-files-on-an-ipad-with-html5/8001076#8001076
			http://stackoverflow.com/questions/8375903/problems-preloading-audio-in-javascript
		wow, yeah, this could possibly all be fixed by doing the sound load in response to a click?  That'd be interesting...
			load() then play() in response to a synchronous click handler
		read http://stackoverflow.com/questions/3619204/how-to-synthesize-audio-using-html5-javascript-on-ipad?rq=1
		read http://stackoverflow.com/questions/5758719/preloading-html5-audio-in-mobile-safari?rq=1

	References:
		https://www.scirra.com/blog/44/on-html5-audio-formats-aac-and-ogg
		http://stackoverflow.com/questions/1007223/which-browsers-support-the-html-5-audio-tag-on-windows-today
		canplay stuff: http://html5doctor.com/native-audio-in-the-browser/
		stalling safari audio: http://stackoverflow.com/questions/4201576/html5-audio-files-fail-to-load-in-safari
		various events: https://www.ibm.com/developerworks/library/wa-ioshtml5/

	A note on waiting for loads to finish, 2017.01.26
		We can't wait for initial sound loads to finish.  We used to do that at boot.
		For one thing, it's tricky on iOS - sounds just don't load, necessarily.
		For another thing, it's rude - should we force the player to wait for all sounds to load before they play?
		
		For another thing, even in Chrome, sometimes on a slow load (seems to be detected on the fly by the browser)
			the sound is only loaded to readyState 1, e.g. metadataloaded.
			We can "hint" how much to preload, but not force it.
			So, unless we want to get a lot more aggressive about it
			(e.g. use the AJAX trick mentioned in one of the links above)
			then we need to accept the idea of running the game before audio has finished loading.
			And in fact we need to accept that some of our sounds will never be ready to play until we actually try to play them.
		So, I've modified isCacheLoaded to just return true;
		
		The trick then is to just play them when needed.
		And, on chrome (55.0 at least), to set currentTime to 0 when playing.
*/

rat.modules.add( "rat.audio.r_audio",
[
	"rat.debug.r_console",
	"rat.os.r_system",
	{name: "rat.audio.r_audio_single", platform: "PS4Browser"}
], 
function(rat)
{
	rat.audio = {	//	audio namespace
		
		initialized : false,
		soundOn: true,
		defaultGroup: "sfx",

		globalVolume: 1,

		groupVolumes:{},
		//cacheLoaded: false,

		toLoadCount: 0,
		loadedCount: 0,
		preferedExt: "", // If no extension was provided, try this one first

		//	some systems (e.g. PS4 browser) work better if we load sequentially
		useSequentialLoad : false,
		activeSequentialLoad : false,	//	are we in the middle of handling load jobs?
		loadJobs: [],	//	load jobs queued up
		
		sounds: {},	//	hashtable of sound info, always accessed by literal sound ID (string)
		dukTapeSound : null,	//	see below

		//	music : [],
		
		//	I'm tracking all audio loading so that I can look back later and see what happened,
		//	even if no text was output at the time.
		trackLog : true,
		logHistory : "",
		
		initialDebug : false,	//	debug initialization and announce types of sound we handle.
		showVerboseLevel : 0,	//	0 = show only error messages.  1 = show init/exit info.  2 = more details.
		
		//	values for showVerboseLevel
		verboseLevelNormal : 0,
		verboseLevelError : 0,
		verboseLevelInit : 1,
		verboseLevelHigh : 2,
		
		//	internal debug logging function
		log : function(txt, verboseLevel, eventType)
		{
			if (!eventType)
				eventType = txt;
			if (!verboseLevel)
				verboseLevel = 1;
			
			if (rat.audio.showVerboseLevel >= verboseLevel)
				rat.console.logOnce( "| r_audio: " + txt, eventType);
			
			rat.audio.logHistory += txt + "\n";
		},
		

		/**
		 *@suppress {undefinedVars} - Don't warn about undefined Audio, as it is defined
		 */
		audioConstructor: function ()
		{
			return new Audio();
		},

		//	one-time system-level init
		//	rAudio argument here is optional.  If not supplied, set up global rat.audio object.
		init: function (useAudioSystem)
		{
			var rAudio;
			if (typeof(useAudioSystem) === 'undefined')
				rAudio = rat.audio;
			else
				rAudio = useAudioSystem;

			//	support setting initialDebug as a shortcut for verbose level 1, which is init/exit stuff.
			if (rat.audio.initialDebug && !rat.audio.showVerboseLevel)
				rat.audio.showVerboseLevel = 1;
			
			rat.audio.log("Init");
			
			rAudio.iOSBrowser = rat.system.has.iOSBrowser;
			rAudio.PS4Browser = rat.system.has.PS4Browser;
			
			//	create an audio object so we can query play abilities
			var myAudio = rAudio.audioConstructor();

			if (myAudio.canPlayType)
			{
				// Currently canPlayType(type) returns: "", "maybe" or "probably" 
				rAudio.canPlayMp3 = !!myAudio.canPlayType('audio/mpeg');	//	mp3
				rAudio.canPlayM4a = !!myAudio.canPlayType('audio/mp4; codecs="mp4a.40.5"');	//	aac
				rAudio.canPlayOgg = !!myAudio.canPlayType('audio/ogg; codecs="vorbis"');	//	ogg
				rAudio.canPlayXMA = !!myAudio.canPlayType('audio/xma2;');					//	xma
				rAudio.canPlayWav = true;	// !!myAudio.canPlayType( 'audio/wav' );			//	wav - also tried with adding  ; codecs="1"  -- to no avail, this returns false on IE and XBO when they will indeed play it
				//	todo: 
				
				if (rAudio.PS4Browser)	//	PS4 browser lies about AAC/MP4/M4a playback ability
					rAudio.canPlayM4a = true;

				if (rAudio.initialDebug)
				{
					rat.audio.log("can play: " +
						 "mp3(" + rAudio.canPlayMp3 + ") " +
						 "m4a(" + rAudio.canPlayM4a + ") " +
						 "ogg(" + rAudio.canPlayOgg + ") " +
						 "xma(" + rAudio.canPlayXMA + ") " +
						 "wav(" + rAudio.canPlayWav + ") " +
						 "\n");
				}
			}

			// [JSalmond May 14, 2014] If i don't add this ref here, my dukTape garbage collector dies when i shutdown, and i don't know why.
			// PLEASE don't remove this
			rAudio.dukTapeSound = myAudio;
			
			rAudio.initialized = true;
		},
		
		//	Load (preload) sounds into the audio system.
		//	Here are some examples of the various ways to use this function.
		//	rat.audio.loadSounds([
		//		{id:'fire', resource:"sounds/fire123.m4a"},	//	normal sound with specified id
		//		{id:'hit', resource:"sounds/hit123", volume:0.8}	//	don't need extension, can specify volume
		//		"sounds/bob.m4a"}	//	just specify resource, in which case id is final filename ('bob')
		//		["sounds/cry.m4a", "sounds/cry2.m4a", "sounds/cry3.m4a"],	//	list of sounds, id is first id ('cry'), and a random one is picked when played
		//		[{id:'cry', resource:"sounds/cry.m4a"}, {resource:"sounds/cry2.m4a"}],	//	alternative form
		//	]);
		//	TODO:  Can we support this function being called several times?  We need to, if it doesn't work already.
		loadSounds: function (sounds)
		{
			//	use our loadSoundList utility function, and have it call our actual sound creator function here.
			//	this separates processing and loading functionality, which makes it easier for other modules to build on this one.
			
			function loadTrigger(res, entry)
			{
				var a = rat.audio.audioConstructor();
					
				if (rat.audio.useSequentialLoad)
				{
					rat.audio.loadJobs.push({a:a, res:res, soundInfo:entry});
				} else {
					rat.audio.doSingleLoad(a, res, entry);	//	trigger actual load
				}

				var vol = 1;
				if (entry.volume !== void 0)
					vol = entry.volume;
				a.entryVolume = vol;
				a.group = entry.group || rat.audio.defaultGroup;
				if( rat.audio.groupVolumes[a.group] !== void 0 )
					vol *= rat.audio.groupVolumes[a.group];
				rat.audio.log("set volume for " + res + " to " + (vol * rat.audio.globalVolume));
				a.volume = vol * rat.audio.globalVolume;
				
				return a;
				
			}
			
			rat.audio.sounds = rat.audio.loadSoundList(sounds, loadTrigger);
			
			if (rat.audio.useSequentialLoad && !rat.audio.activeSequentialLoad)	//	if not already loading, and we have loading to do, do it
			{
				rat.audio.processQueuedLoad();
			}
			
		},
		
		//	INTERNAL function - clients use loadSounds above.
		//	This function processes a sound list, like above, but calls another function to actually load/create each sound.
		//	returns an internal list of sounds.
		loadSoundList : function(sounds, loadTrigger)
		{
			if (!rat.audio.initialized)
			{
				//	If you get here, it's probably because you're initializing your audio module too early,
				//	(e.g. you have it self-initializing as its js file is loading),
				//	and as a result you're calling rat.audio.loadSounds before calling rat.init.
				//	rat audio is initialized from rat.init, and you need to call that before initializing your audio.
				errorLog("!!NOT INITIALIZED before loadSounds", 0);
			}
			
			var mySoundList = {};	//	hash of sounds
			
			//	Order of preference
			var ext;
			if( rat.audio.canPlayM4a )
				ext = ".m4a";
			else if( rat.audio.canPlayMp3 )
				ext = ".mp3";
			else if( rat.audio.canPlayOgg )
				ext = ".ogg";
			else if( rat.audio.canPlayXMA )
				ext = ".xma";
			else if( rat.audio.canPlayWav )
				ext = ".wav";
			else
			{
				rat.audio.log( "Unable to find support audio file format.  Defaulting to .m4a");
				ext = ".m4a";	
			}
			if (rat.audio.initialDebug)
				rat.audio.log("load using " + ext);

			//	Queue up all the sounds we need loaded.
			for (var i = 0; i < sounds.length; i++)
			{
				var pieceList = sounds[i];
				if (!Array.isArray(pieceList))
					pieceList = [sounds[i]];	//	convert each item to an array for convenience

				var id;	//	there will be a single shared id for all sounds in this group
				var list = [];
				var dotPos;
				
				//	loop through all pieces for this ID
				for (var pieceIndex = 0; pieceIndex < pieceList.length; pieceIndex++)
				{
					//	todo put inside another object so we can independently track some runtime variables,
					//	like priority when randomly selecting
					//	right now, we just have actual audio objects.

					var entry = pieceList[pieceIndex];

					var res;
					if (typeof entry === 'string')	//simple case - they just named a resource.  Build an entry object for them.
					{
						res = entry;	//	the resource file name
						dotPos = res.lastIndexOf('.');
						if (dotPos < 0)	//	no extension specified?  Auto-add it.
						{
							dotPos = res.length;
							if( rat.audio.preferedExt )
							{
								if( rat.audio.preferedExt[0] !== "." )
									res += ".";
								res += rat.audio.preferedExt;
							}
						}
						var slashPos = res.lastIndexOf('/');
						if (slashPos < 0)
							slashPos = 0;	//	there isn't one
						else
							slashPos++;	//	skip it
						var resID = res.substring(slashPos, dotPos);	//	in this case the id is filename without path or extension
						entry = { id: resID, resource: res, volume: 1 };	//	build a full entry to simplify code below
					} else
					{	//	normal
						res = entry.resource;
					}

					//	rewrite extension to our standard extension, if we can't play the type specified.
					//	(if we can play the specified type, then that's fine - use that)
					if (res.substring(0, 5) !== 'data:') {
						dotPos = res.lastIndexOf('.');
						if (dotPos < 0)
							dotPos = res.length;
						var extString = res.substring(dotPos, res.length);
						//console.log("extString " + extString);
						if (!((extString === '.mp3' && rat.audio.canPlayMp3) ||
								(extString === '.m4a' && rat.audio.canPlayM4a) ||
								(extString === '.ogg' && rat.audio.canPlayOgg) ||
								(extString === '.xma' && rat.audio.canPlayXMA) ||
								(extString === '.wav' && rat.audio.canPlayWav)))
						{
							//console.log("replacing " + extString + " with " + ext + " because !" + rat.audio.canPlayMp3);
							res = res.substring(0, dotPos) + ext;
						}
						
						//	TEMP DEBUG HACK TEST!
						//res = "http://wahoo.com/play/hosted/tapatoa/" + res;
						
						res = rat.system.fixPath(res);	//	let rat fix up our path, e.g. if we're in some strange hosted environment
					}
					
					rat.audio.toLoadCount++;
					
					var a = loadTrigger(res, entry);
					
					list[pieceIndex] = a;
					
					if (pieceIndex === 0)	//	if this is the first entry in the list, use the same id for later entries.
						id = entry.id;
					
				}	//	end of piece list loop

				if (mySoundList[id])	//	already defined this sound?  If so, this will screw up counts...
				{
					rat.audio.log("ERROR: " + id + " redefined!", 0, id);
					rat.audio.toLoadCount--;
				}
				mySoundList[id] = list;
			}
			
			return mySoundList;

		},	//	end of loadSounds function
		
		/** 
		    Load (preload) sounds into the audio system with the given audio data.
		   
 */
		loadSoundsWithData: function (sounds, audioData)
		{
			function redirectAudioFiles(sounds, audioData) {
				for (var soundIndex = 0; soundIndex < sounds.length; soundIndex++)
				{
					var sound = sounds[soundIndex];
					if (Array.isArray(sound))
						redirectAudioFiles(sound, audioData);
					else
					{
						for (var dataIndex = 0; dataIndex != audioData.length; ++dataIndex) {
							var jsonEntry = audioData[dataIndex];
							if (jsonEntry.id === sound.resource) {
								sound.resource = jsonEntry.resource;
								break;
							}
						}
					}
				}
			}
			redirectAudioFiles(sounds, audioData);
			rat.audio.loadSounds(sounds);
		},
		
		//	handle the next available load job
		processQueuedLoad : function()
		{
			if (!rat.audio.loadJobs || rat.audio.loadJobs.length <= 0)
			{
				rat.audio.log("Done with sequential load.", 2);
				rat.audio.activeSequentialLoad = false;
				return;
			}
			
			rat.audio.log("rat.audio: New sequential load job...", 2);
			
			rat.audio.activeSequentialLoad = true;
			
			var job = rat.audio.loadJobs[0];
			rat.audio.loadJobs.splice(0, 1);
			rat.audio.doSingleLoad(job.a, job.res, job.soundInfo);	//	trigger actual load
		},
		
		//	load a single sound.
		//	This is an internal call, generally not for external use.
		//	Clients:  use loadSounds above, instead.
		doSingleLoad : function (a, res, soundInfo)
		{
			rat.audio.log("triggering load " + soundInfo.id + " : " + res + " at volume " + soundInfo.volume, 2);

			//	iPad is making things really hard.
			//	The behavior we're seeing is this:
			//		sound a starts loading, and is suspended
			//		sound b starts loading, gets to canplaythrough
			//		sound b gets stall event, even though it's fine
			//		sound a never gets another event.
			//	That's the simple case.  If we load lots of files, man, who knows.  Safari goes stupid.
			//	We can't just load() or play() on stall, because it restarts the load, and we loop infinitely?
			//		(plus, we're getting the stall on an object we're already happy with)
			//	2014.06.26	STT:  I found on the PS4 browser that loading in sequence instead of parallel made these problems go away.
			//	That might work on iOS, too, since the problems are the same.
			//	But see above, notes on loading in response to user input.
			
			/**
			 * In this case, this will be the audio object that we bind to the function, but i don't know how to let the linting tool know that
			 * @suppress {globalThis}
			 */
			/*
			function on_stalled(e)
			{
				rat.audio.log('on_stalled');
				//log('. id : ' + soundInfo.id);
				//log('. . . dur: ' + this.duration);
				////log('   error: ' + this.error.code);
				//log('. . . readyState: ' + this.readyState);
			}
			*/

			/**
			 * In this case, this will be the audio object that we bind to the function, but i don't know how to let the linting tool know that
			 * @suppress {globalThis}
			 */
			function on_load(e)
			{
				var ecode = (this.error) ? this.error.code : 'none';
				var message = "on_load, src " + this.src + ", dur " + this.duration + ", error : " + ecode + ", ready : " + this.readyState;
				rat.audio.log(message, 2);
				
				// the callback gets called twice in Firefox if we don't remove the handler
				this.removeEventListener('canplaythrough', on_load, false);
				this.removeEventListener('error', on_error, false);
				//this.removeEventListener('loadedmetadata', track_event, false);
				//this.removeEventListener('canplay', track_event, false);
				
				//	don't remove stalled event?  We get a stall event for loaded sounds instead of the ones
				//	that aren't loading?  iOS sucks.
				//this.removeEventListener('stalled', track_event, false);

				//	todo: move this "ratLoaded" flag into an outer object(audioInfo) that contains this audio object.
				//		don't modify standard object types.
				//		(other flags also need moving?)
				if (!this.ratLoaded)
				{
					this.ratLoaded = true;
					rat.audio.loadedCount++;
					rat.audio.log( rat.audio.loadedCount + "/" + rat.audio.toLoadCount + " loaded: "  + this.src);

					this.ratIsPlaying = false;
					this.ratIsPaused = false;
					this.onplaying = function() {this.ratIsPlaying=true;this.ratIsPaused=false;};
					this.onpause = function() {this.ratIsPlaying=false;this.ratIsPaused=true;};
					
					//	if we're doing sequential loading, trigger the next load
					if (rat.audio.useSequentialLoad)
						rat.audio.processQueuedLoad();
				}
			}

			/**
			 * Called when an audio load failes
			 * @suppress {globalThis}
			 */
			function on_error(e)
			{
				var ecode = (this.error) ? this.error.code : 'none';
				var message = "on_error, src " + this.src + ", dur " + this.duration + ", error : " + ecode + ", ready : " + this.readyState;
				rat.audio.log(message, 0);
				
				//	this will mark this one loaded, so we can move on with the game instead of locking up waiting for load.
				var self = this;
				on_load.call(self, e);
			}
			
			/**
			 * for generally tracking which events are being called...
			 * @suppress {globalThis}
			 */
			function track_event(e)
			{
				var ecode = (this.error) ? this.error.code : 'none';
				var message = "on '" + e.type + "', src " + this.src + ", dur " + this.duration + ", error : " + ecode + ", ready : " + this.readyState;
				rat.audio.log(message, 0);
			}

			a.preload = 'auto';	//	ignored on some systems, e.g. safari ios
			
			//	I should get these events in order:
			//loadstart
			//durationchange
			//loadedmetadata
			//loadeddata
			//progress
			//canplay
			//canplaythrough
			
			//	but we may also care about stalled, suspend, and others.
			
			//	debug tracking
			//a.addEventListener('loadstart', track_event, false);
			//a.addEventListener('stalled', track_event, false);
			//a.addEventListener('loadedmetadata', track_event, false);
			//a.addEventListener('canplay', track_event, false);
			
			/*	some debug crap for testing
			if (res === "http://wahoo.com/play/hosted/tapatoa/audio/waves_short.mp3" ||
				res === "http://wahoo.com/play/hosted/tapatoa/audio/item_g.mp3")
			{
				a.addEventListener('abort', track_event, false);
				a.addEventListener('emptied', track_event, false);
				a.addEventListener('ended', track_event, false);
				a.addEventListener('loadeddata', track_event, false);
				a.addEventListener('pause', track_event, false);
				a.addEventListener('play', track_event, false);
				a.addEventListener('playing', track_event, false);
				a.addEventListener('progress', track_event, false);
				a.addEventListener('ratechange', track_event, false);
				a.addEventListener('seeking', track_event, false);
				a.addEventListener('seeked', track_event, false);
				a.addEventListener('suspend', track_event, false);
				a.addEventListener('waiting', track_event, false);
				a.addEventListener('volumechange', track_event, false);
			}
			*/
			
			//	the ones we actually need
			a.addEventListener('canplaythrough', on_load, false);
			a.addEventListener('error', on_error, false);

			a.src = res;
			
			//	load() is required for safari on ipad, in order to force load.
			//	otherwise safari will not load this sound until I actually try to play it.
			//	maybe in order to save bandwidth...?
			//	bleah, half the time it still won't load it.
			//	May be required on PS4 as well
			a.load();

		},
		
		//	internal utility to pick a random sound (or only sound or whatever) given a sound ID.
		//	This is how we support playing random sounds from a list that share an ID
		
		selectSound : function(soundID)
		{
			if (!rat.audio.soundOn)
				return null;

			if (!rat.audio.sounds[soundID])
			{
				rat.audio.log("tried to play nonexistent sound " + soundID, 0, soundID);
				return null;
			}

			var soundPossibles = rat.audio.sounds[soundID];
			var sound;
			if (soundPossibles.length <= 0)
			{
				rat.audio.log("tried to play from empty list " + soundID, 0, soundID);
				return null;
			}
			else if (soundPossibles.length > 1)
				sound = soundPossibles[(Math.random() * soundPossibles.length) | 0];
			else
				sound = soundPossibles[0];
				
			return sound;
		},

		//	play a sound by ID
		playSound: function (soundID)
		{
			rat.audio.log("play " + soundID);
			var sound = rat.audio.selectSound(soundID);
			if (!sound)
				return;
			
			//	If the sound is paused, this will just restart it.  Fine.
			if (sound.paused)
			{
				sound.play();
				return;
			}
			
			//	otherwise, we always play from the start,
			//	Unless we *just recently* started the sound playing,
			//	in which case, dang, leave it alone, the poor thing.
			//	TODO: per-sound restart window configuration.
			if (sound.currentTime > 0 && sound.currentTime < 0.1)
				return;
			
			//	 note, we need to reset to currentTime 0 even if it already was at 0,
			//	because that's how I'm working around a weird chrome issue with sounds not playing,
			//	and being stuck in readyState 1 (metadataloaded).  setting their time to 0 before playing fixes this.
			//	Eventually.  When Chrome feels good and ready.
			//	Seriously seems like a bug in Chrome.
			//	And it's definitely possible for even this code here to fail to play a sound,
			//	and only a future call will finally trigger the play.
			sound.currentTime = 0;
			// We have no set functionality on currentTime in Wraith, so we call the stop function.
			if (rat.system.has.Wraith)
				sound.stop();
			
			//	and play
			sound.play();
		},

		pauseSound: function (soundID)
		{
			if (!rat.audio.sounds[soundID])
			{
				rat.audio.log("tried to pause nonexistent sound " + soundID, 0, soundID);
				return;
			}

			//	for now, let's pause them all
			//	an alternative would be to see which (if any) is playing, and pause those...
			var soundPossibles = rat.audio.sounds[soundID];
			for (var i = 0; i < soundPossibles.length; i++)
			{
				var sound = soundPossibles[i];
				if (sound && sound.ratIsPlaying)
				{
					//rat.console.log("pause sound " + soundID);
					//rat.console.log(" flag playing " + sound.ratIsPlaying);
					//rat.console.log(" flag paused " + sound.ratIsPaused);
					sound.pause();
				}
			}
		},

		// returns a sound to the starting position
		resetSound: function (soundID)
		{
			if (!rat.audio.sounds[soundID])
			{
				rat.audio.log("tried to reset nonexistent sound " + soundID, 0, soundID);
				return;
			}

			var soundPossibles = rat.audio.sounds[soundID];
			
			//	reset them all
			for (var i = 0; i < soundPossibles.length; i++)
			{
				var sound = soundPossibles[i];
				if (sound && sound.currentTime !== 0)
					sound.currentTime = 0;
			}
		},

		//	stop and reset a sound to starting position
		stopSound: function (soundID)
		{
			rat.audio.pauseSound(soundID);
			rat.audio.resetSound(soundID);
		},
		
		//	seek sound to a certain point
		seekSound: function (soundID, toWhence, isNormalized)
		{
			if (!rat.audio.sounds[soundID])
			{
				rat.audio.log("tried to reset nonexistent sound " + soundID, 0, soundID);
				return;
			}
			
			if (typeof(isNormalized) === 'undefined')
				isNormalized = false;
			
			var soundPossibles = rat.audio.sounds[soundID];
			for (var i = 0; i < soundPossibles.length; i++)
			{
				var sound = soundPossibles[i];
				if (!sound)
					continue;
				var dur = sound.duration;
				var target = toWhence;
				if (isNormalized)
					target *= dur;
				sound.currentTime = target;
			}
			
		},
		
		//	get current volume for this sound.
		//	if more than one sound, return first volume.
		//	TODO: support specifying sub-sound in one of several ways
		//		with sound id in a format like 'soundid:2'
		//		or with a struct that includes id and subindex (if not a struct, just assume id)
		//		or with an additional argument?
		//	and then carry that support to all other applicable functions in a generic way, which includes
		//		support for acting on all subsounds, e.g. set volume for all or pause all...
		//		probably through some "apply to sounds" function system where we pass in an anonymous function
		//	NOTE: With sound groups, this returns the volume set for the sound, not the calculated volume.
		getSoundVolume: function (soundID)
		{
			if (!rat.audio.sounds[soundID])
			{
				rat.audio.log("tried to get volume for nonexistent sound " + soundID, 0, soundID);
				return;
			}

			var soundPossibles = rat.audio.sounds[soundID];
			var sound = soundPossibles[0];
			return sound.entryVolume;
		},
		
		//	Set the volume on a single sound.
		setSoundVolume: function (soundID, volume)
		{
			if (!rat.audio.sounds[soundID])
			{
				rat.audio.log("tried to set volume for nonexistent sound " + soundID, 0, soundID);
				return;
			}

			var soundPossibles = rat.audio.sounds[soundID];
			for (var i = 0; i < soundPossibles.length; i++)
			{
				var sound = soundPossibles[i];
				if (!sound)
					continue;
				if (rat.audio.groupVolumes[sound.group] === void 0)
					rat.audio.groupVolumes[sound.group] = 1;
				sound.entryVolume = volume;
				sound.volume = volume * rat.audio.groupVolumes[sound.group] * rat.audio.globalVolume;
			}
		},
		
		//	Set if a sound is a looping sound
		setSoundLooping: function(soundID, loop)
		{
			loop = !!loop;
			if (!rat.audio.sounds[soundID])
			{
				rat.audio.log("tried to call setSoundLooping nonexistent sound " + soundID, 0, soundID);
				return;
			}
			
			var soundPossibles = rat.audio.sounds[soundID];
			for (var i = 0; i < soundPossibles.length; i++)
			{
				var sound = soundPossibles[i];
				if (!sound)
					continue;
				if (sound.loop != loop)
					sound.loop = loop;
			}
		},

		//	Set the volume of a given audio group
		setGroupVolume: function(group, volume)
		{
			var sound;
			var entry;
			if (volume !== rat.audio.groupVolumes[group])
			{
				rat.audio.groupVolumes[group] = volume;
				for (var id in rat.audio.sounds)
				{
					sound = rat.audio.sounds[id];
					for (var index = 0; index !== sound.length; ++index)
					{
						entry = sound[index];
						if (entry.group !== group)
							continue;
						entry.volume = entry.entryVolume * volume * rat.audio.globalVolume;
					}
				}
			}
		},

		//	Set the global volume level
		setGlobalVolume: function(volume)
		{
			if (volume === rat.audio.globalVolume)
				return;
			rat.audio.globalVolume = volume;
			var sound;
			var entry;
			for (var id in rat.audio.sounds)
			{
				sound = rat.audio.sounds[id];
				for (var index = 0; index !== sound.length; ++index)
				{
					entry = sound[index];
					if (rat.audio.groupVolumes[entry.group] === void 0)
						rat.audio.groupVolumes[entry.group] = 1;
					//var volumeWas = entry.volume;
					entry.volume = entry.entryVolume * rat.audio.groupVolumes[entry.group] * rat.audio.globalVolume;
					//rat.audio.log("changed sound " + id +"-" + index + " to volume " + entry.volume + " from " + volumeWas);
				}
			}
		},

		//	do regular maintenance
		update: function (dt)
		{
		},

		//	how many sounds are registered?  mostly for debug?
		getSoundCount: function ()
		{
			var count = 0;
			for (var key in rat.audio.sounds)
			{
				if (rat.audio.sounds.hasOwnProperty(key))
					count++;
			}
			return count;
		},

		//	get nth sound (mostly for debug?)
		getSoundIDByIndex: function (index)
		{
			var count = 0;
			for (var key in rat.audio.sounds)
			{
				if (rat.audio.sounds.hasOwnProperty(key))
				{
					if (count === index)
						return key;
					count++;
				}
			}
			return 'error';	//	bogus index, return bogus ID
		},

		//	get internal info - mostly debug? should probably not depend on this internal structure staying the same.
		//	OR, it should be well documented and standard that this info will stay the same.
		getSoundInfo: function (id)
		{
			var entry = rat.audio.sounds[id];
			if (entry)
			{
				var first = entry[0];
				var info = {
					id: id,
					duration: first.duration,
					source: first.src,
					volume: first.volume,
					currentPos: first.currentTime,
					readyState: first.readyState,
					errorCode: 'none',
				};
				
				if (first.error)
					info.errorCode = first.error.code;

				return info;
			} else
			{
				rat.audio.log("Requested info for nonexistent sound" + id, 0, soundID);
				return null;
			}
		},
		
		//	what time is this sound at
		getSoundCurrentTime: function (id)
		{
			var entry = rat.audio.sounds[id];
			if (entry)
			{
				var first = entry[0];
				return first.currentTime;
			}
			return 0;
		},

		//	Determines if a given sound is loaded.
		//	From Ethan.  STT is not sure how this works, but hasn't asked Ethan.
		//	see code above that skips this now.
		//	(doesn't seem to be correct on iPad)
		isSoundLoaded: function (id)
		{
			var entry = rat.audio.sounds[id];
			if (!entry)
			{
				rat.audio.log("Tried to check if nonexistent audio (" + id + ") is loaded", 0);
				return null;
			}

			if (entry[0].error !== null)
			{
				// there is an 'error'
				return 'error';
			}

			// https://developer.mozilla.org/en-US/docs/DOM/TimeRanges
			// we'll assume that there is only one range
			var range = entry[0].buffered;

			// log(entry, range, entry[0].duration);

			return (range.length === 1 && range.start(0) === 0 && range.end(0) === entry[0].duration);
		},

		//	return true if all sounds listed in cache are loaded
		isCacheLoaded: function ()
		{
			//	See notes at top of file - we can't really count on this being correct.
			return true;
			
			/*
			//	note:  This will need some more sophistication if we ever support removing sounds
			//log("audio load checking " + rat.audio.loadedCount + " >= " + rat.audio.toLoadCount);
			if (rat.audio.loadedCount >= rat.audio.toLoadCount)
				return true;
			else
			{
				//if (rat.audio.loadStalled)	//	freaking iPad
				if (rat.audio.iOSBrowser)	//	todo:  fix this with sequential loading?
					return true;
				return false;
			}
			*/

			//return rat.audio.cacheLoaded == true;
		},

		//	is this sound actively playing?
		isSoundPlaying: function (id)
		{
			var entry = rat.audio.sounds[id];
			if (entry)
			{
				var first = entry[0];
				//console.log("cp " + first.currentTime);
				if (first.currentTime > 0 && first.currentTime < first.duration)
					return true;
			}
			return false;
		},

		//	what sound listed in cache is not loaded?  mostly for active debugging.
		whatIsMissing: function ()
		{
			var totalCount = 0;
			var missingCount = 0;
			for (var key in rat.audio.sounds)
			{
				if (rat.audio.sounds.hasOwnProperty(key))
				{
					//	each entry can have multiple sounds.
					//	Look at each.
					for (var i = 0; i < rat.audio.sounds[key].length; i++)
					{
						if (!rat.audio.sounds[key][i].ratLoaded)
						{
							rat.console.log("Missing: " + key + " (variant " + i + ")");
							missingCount++;
						}
						totalCount++;
					}
				}
			}
			rat.console.log("missing " + missingCount + " of " + totalCount);
		},

	};

	// called from system init now...  rat.audio.init();
	//@TODO Replace with a registered init function in the module
} );


//--------------------------------------------------------------------------------------------------
//
//	audio test screen for rat engine
//
//	Useful in any game, potentially, for auditioning game-specific sounds registered with rat.
//
//	Feb 2013, Steve Taylor
//
rat.modules.add( "rat.test.r_audio_test_screen",
[
	{ name: "rat.audio.r_audio", processBefore: true },
	
	"rat.math.r_math",
	"rat.ui.r_screen",
	"rat.ui.r_ui_button",
	"rat.ui.r_ui_shape",
	"rat.ui.r_ui_textbox",
	"rat.ui.r_screenmanager",
	"rat.graphics.r_graphics",
	"rat.input.r_input",
],
function(rat)
{
	//
	//	Set up this screen's UI elements, push it on screen stack...
	//	"uiScale" is a factor for how big all the ui elements are.
	//	a 1.0 scale looks OK at 1024x768
	rat.audio.initTestScreen = function (uiScale)
	{
		if (rat.audio.testScreen)	//	already exists
			return;
		
		if (!uiScale)
			uiScale = 1;

		rat.audio.testScreen = new rat.ui.Screen();	//	this screen is a container, and has a bunch of custom properties, too.

		rat.audio.testScreen.modal = true;	//	don't click below me
		rat.audio.testScreen.fullOpaque = true;	//	don't draw below me

		rat.audio.testScreen.name = 'AudioTestScreen';	//	debugging

		//	turn off default button click sound, if any,
		//	because we want to hear the sounds we're auditioning.
		rat.audio.rememberDefaultClick = rat.ui.Button.defaultClickSound;
		rat.ui.Button.defaultClickSound = null;

		rat.audio.buildTestScreenUI(uiScale);

		rat.screenManager.pushScreen(rat.audio.testScreen);
		//	todo: keyboard support for convenience
	};

	//	Kill this audio test screen
	rat.audio.killTestScreen = function ()
	{
		rat.ui.Button.defaultClickSound = rat.audio.rememberDefaultClick;
		rat.screenManager.popScreen();
		rat.audio.testScreen = null;
	};

	//	build audio test screen ui elements, buttons, etc.
	rat.audio.buildTestScreenUI = function (uiScale)
	{
		var screen = rat.audio.testScreen;

		//	start clean
		screen.removeAllSubElements();
		screen.setPos(0, 0);
		screen.setSize(rat.graphics.SCREEN_WIDTH, rat.graphics.SCREEN_HEIGHT);

		//	background
		var back = new rat.ui.Shape(rat.ui.squareShape);
		back.setPos(0, 0);
		back.setSize(rat.graphics.SCREEN_WIDTH, rat.graphics.SCREEN_HEIGHT);
		back.setColor(new rat.graphics.Color(50, 50, 0));
		screen.appendSubElement(back);

		//	title
		var tbox = new rat.ui.TextBox("Audio Test");
		tbox.setPos(0, 0);
		tbox.setSize(rat.graphics.SCREEN_WIDTH, 40 * uiScale);

		tbox.setFont('Arial');
		tbox.setFontSize(24 * uiScale);
		tbox.setColor(rat.graphics.yellow);

		tbox.centerText();
		screen.appendSubElement(tbox);

		//	current text
		tbox = new rat.ui.TextBox("[current]");
		tbox.setPos(50, 30 + 10 * uiScale);
		tbox.setSize(rat.graphics.SCREEN_WIDTH - 50 * 2, 200 * uiScale);

		tbox.setFont('Arial');
		tbox.setFontSize(18 * uiScale);
		tbox.setColor(rat.graphics.white);

		//tbox.centerText();
		screen.appendSubElement(tbox);
		screen.curText = tbox;

		screen.curIndex = 0;
		screen.soundCount = rat.audio.getSoundCount();
		
		//var info = rat.audio.getSoundInfo(0);
		//if (!info.source)
		//	screen.curIndex = 1;

		function updateCurText()
		{
			//screen.curIndex
			var id = rat.audio.getSoundIDByIndex(screen.curIndex);
			var info = rat.audio.getSoundInfo(id);
			var sourceText = info.source;
			if (!sourceText)
				sourceText = "[Load was never triggered]";
				
			var volume = rat.math.floor(info.volume * 100) / 100;
			screen.curText.setTextValue("#" + (screen.curIndex+1) + "/" + screen.soundCount + ": " + id + "\n" +
				"source: " + sourceText + "\n" +
				"duration: " + info.duration + "\n" +
				"volume: " + volume + "\n" +
				"loaded: " + rat.audio.isSoundLoaded(id) + "\n" +
				"current time: " + info.currentPos + "\n" +
				"ready State: " + info.readyState + "\n" +
				"error: " + info.errorCode
			);
		}
		updateCurText();

		// for some reason, overriding tbox's update didn't work
		//	Override screen's update
		//	Do we need to call the old screen.update still?
		//	We do, if we want subelements to update.  Right now, none of them are doing anything useful.
		screen.update = function (dt)
		{
			updateCurText();
		};

		//	Override screen's keydown
		screen.keyDown = function (ratEvent)
		{
			if (ratEvent.which === rat.keys.leftArrow)
				screen.prevSound();
			else if (ratEvent.which === rat.keys.rightArrow)
				screen.nextSound();
			else if (ratEvent.which === rat.keys[' '])
				screen.playSound();
			else
				return false;

			return true;
		};

		screen.prevSound = function ()
		{
			screen.curIndex--;
			if (screen.curIndex < 0)
				screen.curIndex = screen.soundCount - 1;	//	wrap
			updateCurText();
		};

		screen.nextSound = function ()
		{
			screen.curIndex++;
			if (screen.curIndex >= screen.soundCount)
				screen.curIndex = 0;	//	wrap
			updateCurText();
		};

		screen.playSound = function ()
		{
			var id = rat.audio.getSoundIDByIndex(screen.curIndex);
			rat.audio.playSound(id);
		};

		var butWidth = 100 * uiScale;
		var butHeight = 30 * uiScale;
		
		function addButton(text, x, y, uiScale, id, func)
		{
			var b = rat.ui.makeCheapButton(null, new rat.graphics.Color(150, 150, 200));
			b.setTextValue(text);
			b.setSize(butWidth, butHeight);
			b.setPos(x, y);
			b.setCallback(func, id);
			
			screen.appendSubElement(b);
			
			//	some other text thing?
			//var tbox = new rat.ui.TextBox("[current]");
			//tbox.setPos(50, 100);
			//tbox.setSize(rat.graphics.SCREEN_WIDTH - 50 * 2, 200);

			//tbox.setFont('Arial');
			//tbox.setFontSize(18);
			//tbox.setColor(rat.graphics.white);
		}

		//var left_margin = (rat.graphics.SCREEN_WIDTH - (90 + 20) * 6) - 100; // each button is 90 wide;
		var left_margin = 30;
		var left_offset = 0;
		function getSpot()
		{
			var x = left_margin + left_offset + butWidth /2;
			left_offset += ((butWidth * 1.1)|0);
			return x;
		}

		var audioID;
		var yPos = rat.graphics.SCREEN_HEIGHT - 30 - butHeight;
		addButton("Prev", getSpot(), yPos, uiScale, 'prev', function(e, eid) {screen.prevSound();});
		addButton("Next", getSpot(), yPos, uiScale, 'next', function(e, eid) {screen.nextSound();});
		addButton("Play", getSpot(), yPos, uiScale, 'play', function(e, eid) {screen.playSound();});
		addButton("Pause", getSpot(), yPos, uiScale, 'pause', function(e, eid) {
			var id = rat.audio.getSoundIDByIndex(screen.curIndex);
			rat.audio.pauseSound(id);
		});
		addButton("Stop", getSpot(), yPos, uiScale, 'stop', function(e, eid) {
			audioID = rat.audio.getSoundIDByIndex(screen.curIndex);
			rat.audio.pauseSound(audioID);
			rat.audio.resetSound(audioID);
		});

		addButton("Done", getSpot(), yPos, uiScale, 'done', function(e, eid) {rat.audio.killTestScreen();});
		
		//	skipping around in sound tests
		left_offset = 0;
		yPos -= butHeight + 8 * uiScale;
		addButton("|<", getSpot(), yPos, uiScale, 'tostart', function(e, eid) {
			audioID = rat.audio.getSoundIDByIndex(screen.curIndex);
			rat.audio.pauseSound(audioID);
			rat.audio.seekSound(audioID, 0);
			rat.audio.playSound(audioID);
		});
		addButton("1/4", getSpot(), yPos, uiScale, 'toonefourth', function(e, eid) {
			audioID = rat.audio.getSoundIDByIndex(screen.curIndex);
			rat.audio.pauseSound(audioID);
			rat.audio.seekSound(audioID, 0.25, true);
			rat.audio.playSound(audioID);
		});
		addButton("2/4", getSpot(), yPos, uiScale, 'tohalf', function(e, eid) {
			audioID = rat.audio.getSoundIDByIndex(screen.curIndex);
			rat.audio.pauseSound(audioID);
			rat.audio.seekSound(audioID, 0.5, true);
			rat.audio.playSound(audioID);
		});
		addButton("3/4", getSpot(), yPos, uiScale, 'tothreefourths', function(e, eid) {
			audioID = rat.audio.getSoundIDByIndex(screen.curIndex);
			rat.audio.pauseSound(audioID);
			rat.audio.seekSound(audioID, 0.75, true);
			rat.audio.playSound(audioID);
		});
		addButton(">|", getSpot(), yPos, uiScale, 'toend', function(e, eid) {
			audioID = rat.audio.getSoundIDByIndex(screen.curIndex);
			rat.audio.seekSound(audioID, 1, true);
		});
		
		//	volume and other effects
		left_offset = 0;
		yPos -= butHeight + 8 * uiScale;
		addButton("< vol", getSpot(), yPos, uiScale, 'voldown', function(e, eid) {
			audioID = rat.audio.getSoundIDByIndex(screen.curIndex);
			var vol = rat.audio.getSoundVolume(audioID);
			vol -= 0.1;
			if (vol < 0)
				vol = 0;
			rat.audio.setSoundVolume(audioID, vol);
		});
		addButton("vol >", getSpot(), yPos, uiScale, 'volup', function(e, eid) {
			audioID = rat.audio.getSoundIDByIndex(screen.curIndex);
			var vol = rat.audio.getSoundVolume(audioID);
			vol += 0.1;
			if (vol > 1)
				vol = 1;
			rat.audio.setSoundVolume(audioID, vol);
		});
		
	};
} );
//--------------------------------------------------------------------------------------------------
//
//	Node Graph generation module
//	
//	A node graph is a set of connected nodes, like a map of stars with jumpgates.
//	This module generates an interesting nodegraph algorithmically, using
//	a bunch of configuration parameters.
//
//
/*
	USAGE:
	
	Setup:
		var nodeGraph = new rat.NodeGraph();
		
		nodeGraph.setConfig({	//	see "defConfig" below for many default values
			targetNodeCount : 24,	//	number of nodes we want to make, total (approximate, varies by linkFromCurrent)
		});
		
		nodeGraph.build();
		
	Then:
		nodeGraph.maxGeneration : is the maximum generation count (steps from start, which has a generation of 0)
		
		nodeGraph.getBounds() gets bounds of nodegraph
		
		nodeGraph.nodes : is an array of nodes. each node has
			pos : xy position (in nodegraph space, where nodes are usually around 1 unit apart)
			generation : number of steps from start node (could be a difficulty or depth or something)
			links : array of indices of nodes to which we are directly linked
			leaf : true if this is a leaf node (has only one link to the graph, and is not the starting node)
		
	todo: encapsulate nicely
	serialize/deserialize functions
	todo: move math utils elsewhere
	todo: calculate chokepoints
	todo: calculate leaves
	todo: optionally make target node count absolute - that's a lot more useful.

	How this works:
		We have a list of nodes on a conceptual grid of 1x1 units.
		We start at one node and grow from there, adding nodes, linking nearby nodes, according to config
		avoiding overlapping nodes, overlapping links, crossed links, etc.
*/

rat.modules.add("rat.utils.r_nodegraph",
[
	"rat.math.r_vector",
	"rat.graphics.r_graphics",
	"rat.utils.r_shapes",
	"rat.debug.r_console",
],
function (rat)
{
	/** 
	    NodeGraph
	    @constructor 
	   
 */
	rat.NodeGraph = function ()
	{
		this.nodes = [];	//	actual map
		this.mapCenter = new rat.Vector(0, 0);
		this.mapSize = new rat.Vector(1, 1);
		this.config = null;
		this.fillDefaultConfig();
	};

	rat.NodeGraph.debug = {	//	debug drawing stuff
		nodeColors: [
			"red",
			"yellow",
			"green",
			"cyan",
			"blue",
			"violet",
			"orange",
		],
		NODE_COLOR_COUNT: 7
	};

	rat.NodeGraph.prototype.fillDefaultConfig = function ()
	{
		//	clean up some default stuff.
		if (!this.config)
			this.config = {};

		//	does this stuff work in compiler?  So annoying...
		//	If not, maybe set config to defaults and have client set their values AFTER that.
		//	but this is better 'cause we can fix client errors if we need to, or otherwise interpret..

		var defConfig = {

			rng : Math,	//	default, use built-in random()
			
			targetNodeCount: 50,	//	number of nodes we want to make, total (approximate, varies by linkFromCurrent)

			//	creating new nodes from current node
			linkFromCurrent: 3,	//	add several nodes from current node. high value = spiky burst patterns, low = long chains
			attemptsAtNearSpace: 5,	//	how many times to try to find near space
			nearSpaceDist: 3,		//	how near to attempt (+/-) (in units) (1 = very tight little lattices)
			nearSpaceBias: new rat.Vector(0, 0),	//	drift in one direction - this helps make maps more linear and less curly
			//	probably make nearSpaceBias less than nearSpaceDist, but it's not required.
			//	also note that a high bias results in fewer links back, generally, just 'cause they're not near enough.

			//	backlinking to existing near nodes after creating a new node:
			//	This first number : a higher number means more of a mesh, more paths, more loops.
			//	this number can be 0, resulting in no new links.  I think random*2 is a good range for a space trading game
			maxTargetNewLinks: 2,	//	how many links we would like to have to (randomly varied from 0 to this value)
			maxNearNodesToTry: 4,	//	lower = fewer links
			maxNearNodeDistance: 4,	//	these affect how far away nodes are connected in the final map
			minNearNodeDistance: 0,
			maxLinkGenerationGap: -1,	//	if the node we're trying to link back to is more than X generations back in the list, forget it.
			//	this helps loops be localized.
			//	0 is a valid value - it means same-generation nodes (branches from same parent) can be linked.
			//	1 works well - it means each link only goes up or down by one generation, e.g. 6->7->6->7->8->9
			//	-1 is no limit

			//	special rules
			maxLinksFromFirst: 1,	//	max number of links from first node
			maxLinksFromLast: 1,	//	max number of links to last node

			//	what to do when there are problems
			stuckAction: 'backtrack',	//	'backtrack', 'random', or none

			//	adjust when done
			addRandomJitter: true,	//	randomly shift nodes a bit when done so they don't look like they're on a grid
			adjustToTopLeft: false,	//	readjust to put top-left edges at 0 and 0
			
			//unused: recenterAtZero: false,		//	recenter all at 0,0
		};

		for (var c in defConfig)
		{
			if (typeof (this.config[c]) === 'undefined')
				this.config[c] = defConfig[c];
		}
	};

	rat.NodeGraph.prototype.setConfig = function (config)
	{
		this.config = config;
		this.fillDefaultConfig();
	};

	//	do these two lines intersect?
	//	NOTE:  This function won't return an intersect if the ends exactly line up,
	//	which is useful for adding one line on the end of another without considering them intersecting...
	function linesIntersect(x1, y1, x2, y2, x3, y3, x4, y4)
	{
		var numA = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
		var numB = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3);
		var denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);

		if (denom === 0.0)
		{
			//	lines are the same or parallel - count that as not intersecting?  hmm...
			return false;
		}
		else
		{
			var ua = numA / denom;
			var ub = numB / denom;
			var inRange1 = (ua > 0.0 && ua < 1.0);
			var inRange2 = (ub > 0.0 && ub < 1.0);
			if (inRange1 && inRange2)
			{
				//intersectX = Math.floor(x1 + ua*(x2-x1)+0.5);
				//intersectY = Math.floor(y1 + ua*(y2-y1)+0.5);
				return true;
			}
			else
			{
				return false;
			}
		}
	}

	//	distance from a point to a line segment...
	//	http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
	function sqr(x) { return x * x; }
	function dist2(v, w) { return sqr(v.x - w.x) + sqr(v.y - w.y); }
	function distToSegmentSquared(p, v, w)
	{
		var l2 = dist2(v, w);
		if (l2 === 0)
			return dist2(p, v);
		var t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2;
		if (t < 0)
			return dist2(p, v);
		if (t > 1)
			return dist2(p, w);
		return dist2(p, {
			x: v.x + t * (w.x - v.x),
			y: v.y + t * (w.y - v.y)
		});
	}
	//function distToSegment(p, v, w)
	//{
	//	return Math.sqrt(distToSegmentSquared(p, v, w));
	//}

	/**
	//
	//	Check if this new proposed line runs close to existing nodes anywhere.
	//	(except the given node)
	//
	* @param {number=} skipNode1
	* @param {number=} skipNode2
	*/
	rat.NodeGraph.prototype.checkLineNearNodes = function (curX, curY, x, y, skipNode1, skipNode2)
	{
		//console.log("checkLineNearNodes");
		var v = { x: curX, y: curY };
		var w = { x: x, y: y };
		for (var i = 0; i < this.nodes.length; i++)
		{
			if (i === skipNode1 || i === skipNode2)
				continue;
			var d = distToSegmentSquared(this.nodes[i].pos, v, w);
			//console.log(" (" + this.nodes[i].pos.x + "," + this.nodes[i].pos.y + ") .. (" +v.x + "," +v.y +")->(" + w.x + "," + w.y + ")");
			//console.log("  d = " + d);
			//	this value, 0.2 (used also below) was reached by experiment.  A smaller number would let nodes get closer.
			//	a higher number will keep them from getting too packed.
			if (d < 0.2)
			{
				//console.log("too close!");
				return true;
			}
		}
		return false;
	};

	//
	//	Check if this new proposed node is close to existing lines anywhere.
	//
	rat.NodeGraph.prototype.checkNodeNearLines = function (x, y)
	{
		//console.log("checkNodeNearLines");
		var p = { x: x, y: y };

		for (var i = 0; i < this.nodes.length; i++)
		{
			var n = this.nodes[i];
			for (var linkIndex = 0; linkIndex < n.links.length; linkIndex++)
			{
				var d = distToSegmentSquared(p, n.pos, this.nodes[n.links[linkIndex]].pos);
				//fix refs here before uncommenting: console.log(" (" + this.nodes[i].pos.x + "," + this.nodes[i].pos.y + ") .. (" +v.x + "," +v.y +")->(" + w.x + "," + w.y + ")");
				//console.log("  pd = " + d);
				if (d < 0.2)
				{
					//console.log(" p too close...");
					return true;
				}
			}
		}
		return false;
	};

	//
	//	check if this new proposed line is close in angle to any existing link to this node.
	//	This is not solving our problems...
	//
	/*
	function checkLinesClose(x1, y1, x2, y2, nodeIndex)
	{
		var angle = Math.atan2(y2-y1, x2-x1);
		var node = this.nodes[nodeIndex];
		for (var i = 0; i < node.links.length; i++)
		{
			var link = this.nodes[nodeIndex].links[i];
			var langle = Math.atan2(this.nodes[link].pos.y - node.pos.y, this.nodes[link].pos.x - node.pos.x);
			if (Math.abs(langle - angle) < 0.1)
			{
				rat.console.log("too close!");
				return true;
			}
		}
		return false;
	};
	*/

	//	check for used space in nodes
	rat.NodeGraph.prototype.checkUsedSpace = function (x, y)
	{
		for (var i = 0; i < this.nodes.length; i++)
		{
			if (this.nodes[i].pos.x === x && this.nodes[i].pos.y === y)
				return true;
		}

		return false;
	};

	/**
	//	check if this line intersects with any existing links.
	//	skipping a node is useful because we might be projecting a line from that node,
	//	and this code will detect that as an intersect since one endpoint is the same.
	//	But then I changed linesIntersect to ignore matching ends, so... forget that?
	*	@param {number=} skipNode optional node index to skip
	*/
	rat.NodeGraph.prototype.checkIntersects = function (sx, sy, x, y, skipNode)
	{
		for (var cIndex = 0; cIndex < this.nodes.length; cIndex++)
		{
			//if (cIndex == skipNode)
			//	continue;

			var cNode = this.nodes[cIndex];
			for (var linkIndex = 0; linkIndex < cNode.links.length; linkIndex++)
			{
				//	all links are doubled - only check links from lower to higher, to skip the duplicates.
				var tNodeIndex = cNode.links[linkIndex];
				if (tNodeIndex !== skipNode && tNodeIndex > cIndex)
				{
					var tNode = this.nodes[tNodeIndex];
					if (linesIntersect(sx, sy, x, y, cNode.pos.x, cNode.pos.y, tNode.pos.x, tNode.pos.y))
					{
						//console.log("isect: (" + sx + "," + sy + ")->(" + x + "," + y + ") x (" + cNode.x + "," + cNode.y + ")->(" + tNode.x + "," + tNode.y + ")");
						return true;
					}
				}
			}
		}
		return false;
	};

	//
	//	get a full list of nodes, in order of nearness to a certain node.
	//	exclude the subject node.
	//	exclude any nodes farther than the given generation gap
	//
	rat.NodeGraph.prototype.getNearNodeIndices = function (nodeIndex, maxGenerationGap)
	{
		var refNode = this.nodes[nodeIndex];
		refNode.distSq = 0;
		var list = [];
		for (var i = 0; i < this.nodes.length; i++)
		{
			var n = this.nodes[i];
			//	yes, I suck, I'm tacking fields on to the base structure for temporary purposes
			n.index = i;
			n.distSq = n.pos.distanceSqFrom(refNode.pos);

			if (maxGenerationGap >= 0)	//	check generation gap?
			{
				var generationGap = Math.abs(refNode.generation - n.generation);
				//console.log("checking gap " + generationGap);
				if (generationGap > maxGenerationGap)
				{
					//console.log("rejecting gap " + generationGap);
					continue;
				}
			}

			list[list.length] = n;
		}

		list.sort(function (a, b)
		{
			return a.distSq - b.distSq;
		});

		return list;
	};

	//	link these two nodes together, both ways.
	rat.NodeGraph.prototype.linkNodes = function (a, b)
	{
		this.nodes[a].links[this.nodes[a].links.length] = b;
		this.nodes[b].links[this.nodes[b].links.length] = a;

		//	debug checking
		//if (this.nodes[a].generation == 0 || this.nodes[b].generation == 0)
		//{
		//	console.log("gen 0");
		//}
		//var ggap = Math.abs(this.nodes[a].generation - this.nodes[b].generation);
		//if (ggap > 1)
		//{
		//	console.log("GGAP BIG: " + ggap);
		//}
	};

	//	return true if these two nodes are linked
	rat.NodeGraph.prototype.nodesHaveLink = function (a, b)
	{
		for (var linkIndex = 0; linkIndex < this.nodes[a].links.length; linkIndex++)
		{
			if (this.nodes[a].links[linkIndex] === b)
				return true;
		}
		return false;
	};

	//	link back to some nearby (older) nodes from this node.
	//	called below after each node is created, but could be used other ways in theory.
	rat.NodeGraph.prototype.linkNearNodes = function (nodeIndex, maxNewLinks)
	{
		//	now many new links to create, if there's room.
		//	This number strongly affects the final map - a higher number means more of a mesh, more paths, more loops.
		//	this number can be 0, resulting in no new links.  I think 0-1 is a good range for a space trading game
		var targetNewLinks = Math.floor(this.config.rng.random() * (maxNewLinks + 1));
		//console.log("target " + targetNewLinks);
		if (targetNewLinks === 0)
			return;

		var checkNode = this.nodes[nodeIndex];	//	convenience reference to our node

		//	get full list of nodes sorted by nearness
		var list = this.getNearNodeIndices(nodeIndex, this.config.maxLinkGenerationGap);

		//	go through first few nodes in list (nearest nodes to us)
		//	if we're not already linked, or a link would not intersect another link,
		//	then add a link.  Try to reach our target new link count.
		var maxNearNodesToTry = this.config.maxNearNodesToTry;
		var maxDist = this.config.maxNearNodeDistance;
		var minDist = this.config.minNearNodeDistance;

		for (var i = 1; i <= maxNearNodesToTry && i < list.length && targetNewLinks > 0; i++)	//	skip first, since that's us
		{
			//console.log("n " + nodeIndex + "+" + list[i].index + " dist = " + list[i].distSq);

			if (list[i].distSq >= minDist && list[i].distSq < maxDist &&
				!this.nodesHaveLink(nodeIndex, list[i].index) &&	//	already there?
				!this.checkIntersects(list[i].pos.x, list[i].pos.y, checkNode.pos.x, checkNode.pos.y) &&	//	crossing another line?
				!this.checkLineNearNodes(list[i].pos.x, list[i].pos.y, checkNode.pos.x, checkNode.pos.y, nodeIndex, list[i].index)	//	new link would be too near another node (except these 2)?
				)
			{
				//	special rule for limiting number of links to first node
				//console.log("special check: " + nodeIndex + " to " + list[i].index + ": has " + list[i].links.length);
				if (list[i].index === 0 && list[i].links.length >= this.config.maxLinksFromFirst)
					continue;

				//console.log("added");
				this.linkNodes(nodeIndex, list[i].index);
				targetNewLinks--;
			}
		}
	};

	//	shift the locations of nodes a little, just for looks.  Leave links alone.
	rat.NodeGraph.prototype.addRandomMapJitter = function ()
	{
		var rand = this.config.rng.random;
		for (var i = 0; i < this.nodes.length; i++)
		{
			this.nodes[i].pos.x += (rand() * 0.2 - 0.1);
			this.nodes[i].pos.y += (rand() * 0.2 - 0.1);
		}
	};

	//
	//	build the nodegraph according to config.
	//	This can be called again on an existing nodegraph to regenerate it.
	//
	rat.NodeGraph.prototype.build = function ()
	{
		this.nodes = [];	//	start clean in case this is a rebuild

		//	initial node
		this.nodes[0] = {
			pos: new rat.Vector(0, 0),	//	some interesting starting pos, but doesn't really matter (map grows in random directions)
			links: [],		//	list of node indices to which we are linked
			colorIndex: 0,	//	mostly for fun, for now
			generation: 0,	//	number of steps from root node
			main: true,		//	part of main path (wasn't a branch)
			leaf: false,
		};

		var curNode = 0;
		var generation = 1;
		var i;

		//	here's the main loop for adding new nodes one at a time
		for (var tries = 0; this.nodes.length < this.config.targetNodeCount; tries++)
		{
			var curX = this.nodes[curNode].pos.x;
			var curY = this.nodes[curNode].pos.y;

			var lastNode = curNode;
			var nodeAdded = 0;	//	track if we added a new node (won't be 0, if we did)

			//	add several nodes connected from here
			//	this number could change to affect output, or it could even be randomly picked for each node instead of fixed.
			//	A high number here results in spiky burst patterns
			var linkFromCurrent = this.config.linkFromCurrent;

			//	special rule for limiting links to/from first node
			if (this.config.maxLinksFromFirst < linkFromCurrent && curNode === 0)
				linkFromCurrent = this.config.maxLinksFromFirst;

			for (var sIndex = 0; sIndex < linkFromCurrent; sIndex++)
			{
				//	try a few times to find a legit spot for the new node
				//	note that it's possible not to find a spot, which could mean drastically different generated maps, depending on randomness
				//	changing this number won't affect much - a higher number means trying harder, taking a little longer.
				//	I suppose changing this lower would result in less strung-out maps?
				for (var fIndex = 0; fIndex < this.config.attemptsAtNearSpace; fIndex++)
				{
					//	OK, this is interesting.
					//	We actually end up with cleaner maps if we make this a little lopsided.
					//	They curl back on themselves less...
					//	so, I added this "bias" idea..
					var x = curX + Math.floor(this.config.rng.random() * this.config.nearSpaceDist * 2) - this.config.nearSpaceDist + this.config.nearSpaceBias.x;
					var y = curY + Math.floor(this.config.rng.random() * this.config.nearSpaceDist * 2) - this.config.nearSpaceDist + this.config.nearSpaceBias.y;
					if (x === 0 && y === 0)
						continue;
					//console.log("checking cases for " + curNode);
					if (!this.checkUsedSpace(x, y) && //	node at this spot?
						!this.checkIntersects(curX, curY, x, y, curNode) &&	//	new link would cross other links?
						!this.checkLineNearNodes(curX, curY, x, y, curNode) &&	//	new link would be too near another node?
						!this.checkNodeNearLines(x, y))	//	new node would be too near another link?
					{
						//	all good - add the new node!
						//console.log("OK");
						var node = {
							pos: new rat.Vector(x, y),
							links: [],
							colorIndex: Math.floor(this.config.rng.random() * rat.NodeGraph.debug.NODE_COLOR_COUNT),
							generation: generation,
							main: false,		//	not part of main path yet - depends on whether more is built from here
							leaf: false,		//	by default, not a leaf - will get detected when we're done
						};
						this.nodes[this.nodes.length] = node;
						this.linkNodes(curNode, this.nodes.length - 1);
						nodeAdded = this.nodes.length - 1;

						this.nodes[curNode].main = true;	//	we've successfully built from here, so current base is part of main line

						//	try to link this new node to other existing nearby nodes, as well.
						//	so the map has some nice double-links and loops.
						var backLinks = this.config.maxTargetNewLinks;

						if (this.nodes.length >= this.config.targetNodeCount)	//	will be last node
						{
							//	node already has one link - back to source.
							//console.log("last node...");
							if (this.config.maxLinksFromLast - 1 < backLinks)
								backLinks = this.config.maxLinksFromLast - 1;
						}
						//console.log("backLinks " + backLinks);

						if (backLinks)
							this.linkNearNodes(this.nodes.length - 1, backLinks);

						break;
					}	//	end of good check and new node added
				}	//	end of near space checks
			}	//	end of loop to add multiple new nodes

			//	IF we created any new nodes, use the new node as anchor.
			//	if we did not, we need to make sure not to update curNode, as we might be backtracking... see below
			if (nodeAdded)
				curNode = nodeAdded;	//	move to this node as an anchor for new nodes

			if (curNode === lastNode)	//	same as before, wasn't able to create a single new node...
			{
				//rat.console.log("Boxed in at " + curNode + "... stuck action: " + this.config.stuckAction);
				if (this.config.stuckAction === 'random')
				{
					//	try picking a random previous node to work with...
					curNode = Math.floor(this.config.rng.random() * this.nodes.length);

				} else if (this.config.stuckAction === 'backtrack')
				{
					curNode--;
					//rat.console.log("backtracking to " + curNode);
					if (curNode <= 1)	//	man, serious problems...  I don't think this is possible or likely
						break;	//	done
				} else
				{	//	no stuck behavior - just stop give up...
					break;	//	done
				}
				generation = this.nodes[curNode].generation;	//	roll back generation to match
				//	todo: if we step back to a leaf and it works, mark it as main?

				//	NOTE:  At this point, we have to get unstuck.
				//	Walking back (or randomly jumping back) is our only solution, currently.
				//	Rolling back the generation counter as well is the right thing to do in theory, and it avoids generation gap problems,
				//	but it means potentially that our generations don't get very high, if there's a lot of backtracking.
				//	though, that already kinda happens with multiple links off a node.  Hmm...
				//	one option would be to generate nodes until we hit a target generation instead of a target total count,
				//	but I'm not sure if that's desirable.  Would probably depend on the specific use of the resulting nodegraph.

			}	//	end of detected stuck

			generation++;	//	next generation will be one beyond ours
		}
		//rat.console.log("ng created " + this.nodes.length + " nodes.");

		this.nodes[this.nodes.length - 1].main = true;	//	mark final node as part of main line (from start to end)

		//	mark explicit leaves
		for (i = 0; i < this.nodes.length; i++)
		{
			if (this.nodes[i].links.length === 1 && !this.nodes[i].main)
				this.nodes[i].leaf = true;
		}

		//linkNearNodes(0);	//	test

		if (this.config.addRandomJitter)
			this.addRandomMapJitter();	//	for looks, shift nodes around a bit

		//	find bounds so we know where the center is
		var minX = 99999;
		var maxX = -99999;
		var minY = 99999;
		var maxY = -99999;
		for (i = 0; i < this.nodes.length; i++)
		{
			if (this.nodes[i].pos.x < minX)
				minX = this.nodes[i].pos.x;
			if (this.nodes[i].pos.x > maxX)
				maxX = this.nodes[i].pos.x;
			if (this.nodes[i].pos.y < minY)
				minY = this.nodes[i].pos.y;
			if (this.nodes[i].pos.y > maxY)
				maxY = this.nodes[i].pos.y;
		}
		this.mapSize.x = maxX - minX;
		this.mapSize.y = maxY - minY;
		this.mapLargestSide = (this.mapSize.x > this.mapSize.y) ? this.mapSize.x : this.mapSize.y;
		this.mapCenter.x = minX + this.mapSize.x / 2;
		this.mapCenter.y = minY + this.mapSize.y / 2;
		//console.log("center " + this.mapCenter.x + "," + this.mapCenter.y);

		//	readjust everything so top-left is 0,0
		if (this.config.adjustToTopLeft)
		{
			for (i = 0; i < this.nodes.length; i++)
			{
				this.nodes[i].pos.x -= minX;
				this.nodes[i].pos.y -= minY;
			}
			this.mapCenter.x -= minX;
			this.mapCenter.y -= minY;
		}

		this.maxGeneration = generation - 1;	//	remember highest "generation" we reached.
	};

	rat.NodeGraph.prototype.getBounds = function ()
	{
		var r = new rat.shapes.Rect(this.mapCenter.x - this.mapSize.x / 2, this.mapCenter.y - this.mapSize.y / 2, this.mapSize.x, this.mapSize.y);
		return r;
	};

	//	draw map - for debug use
	rat.NodeGraph.prototype.draw = function (ctx, fitRect, center, forceScale)
	{
		var nodeScale = 10;
		var bufferSpace = nodeScale * 2;
		var n;
		var x;
		var y;
		var i;
		var mapScale = (fitRect.w - bufferSpace * 2) / (this.mapSize.x);
		var altScale = (fitRect.h - bufferSpace * 2) / (this.mapSize.y);
		if (altScale < mapScale)
			mapScale = altScale;
		if (forceScale)
		{
			mapScale = forceScale;
			bufferSpace = 0;
		}

		//var mapScale = 30 * (10 / this.mapLargestSide);

		//var offsetX = fitRect.x + 250 - this.mapCenter.x * mapScale;
		//var offsetY = fitRect.y + 200 - this.mapCenter.y * mapScale;
		var offsetX = bufferSpace;
		var offsetY = bufferSpace;
		if (center)
		{
			offsetX = fitRect.x + fitRect.w / 2 - this.mapCenter.x * mapScale;
			offsetY = fitRect.y + fitRect.h / 2 - this.mapCenter.y * mapScale;
		} else
		{
			offsetX = fitRect.x;
			offsetY = fitRect.y;
		}

		rat.graphics.save(ctx);
		rat.graphics.translate(offsetX, offsetY, ctx);

		ctx.fillStyle = "#6040FF";
		//	first draw nodes
		for (i = 0; i < this.nodes.length; i++)
		{
			var size = nodeScale;
			//	temp debug stuff
			/*
			if (i == 0)
				ctx.fillStyle = "#F04030";
			else if (i == 1)
				ctx.fillStyle = "#F0F030";
			else if (i == 2)
				ctx.fillStyle = "#00F0F0";
			else
				ctx.fillStyle = "#6040FF";
			*/
			if (i === 0)	//	show first node differently
				size = nodeScale * 2;
			if (i === this.nodes.length - 1)	//	show last node differently
				size = nodeScale * 2;

			n = this.nodes[i];
			x = n.pos.x * mapScale;
			y = n.pos.y * mapScale;

			ctx.fillStyle = rat.NodeGraph.debug.nodeColors[n.colorIndex];

			rat.graphics.save();
			rat.graphics.translate(x, y, ctx);
			ctx.beginPath();
			ctx.arc(0, 0, size / 2, 0, Math.PI * 2, true);
			ctx.closePath();
			ctx.fill();
			rat.graphics.restore();

			ctx.font = "10px Arial";
			ctx.fillStyle = "#A0A0A0";
			ctx.fillText(n.generation, x + 5, y - 2);
		}

		//	then another pass for drawing links
		for (i = 0; i < this.nodes.length; i++)
		{
			n = this.nodes[i];
			x = n.pos.x * mapScale;
			y = n.pos.y * mapScale;

			ctx.strokeStyle = "yellow";
			for (var linkIndex = 0; linkIndex < n.links.length; linkIndex++)
			{
				//	to only draw links once, only draw from smaller indices to bigger indices, and not vice-versa
				if (n.links[linkIndex] < i)
					continue;

				var n2 = this.nodes[n.links[linkIndex]];
				var x2 = n2.pos.x * mapScale;
				var y2 = n2.pos.y * mapScale;

				ctx.beginPath();
				ctx.moveTo(x, y);
				ctx.lineTo(x2, y2);
				ctx.closePath();
				ctx.stroke();
			}
		}

		var r = this.getBounds();
		ctx.strokeStyle = "#55FF55";
		ctx.strokeRect(r.x * mapScale, r.y * mapScale, r.w * mapScale, r.h * mapScale);

		rat.graphics.restore();	//	restore from center offset translation

		//	show fitrect - how well did we do?
		ctx.strokeStyle = "#5555FF";
		ctx.strokeRect(fitRect.x, fitRect.y, fitRect.w, fitRect.h);


	};
});
//
//	Firebase implementation of our standard storage API.
//
//	Usage:
//
//		get a firstbase storage reference explicitly.
//		Unlike other storage implementations, we don't hand one of these back from rat.storage.getStorage(),
//		because it's a more specialized case.  So, just create your own reference like this:
//
//			var storage = new rat.FirebaseStorage('address', 'prefix', userID);
//
//		address should be something like : "https://holiday.firebaseio.com/whatever/storage/"
//		
//		very important that you provide a reasonable specific path there, because we immediately request all data under that path.
//
//		if user ID is not specified, a common shared storage is used.
//			todo: support automatic user ID assignment like r_telemetry?
//
//		then use normally:
//
//			storage.setItem("hey", 12);
//			var x = storage.getItem("hey");
//			storage.setObject("frank", { age: 12, friends: 3 });
//
//		Note that you'll need a firebase.js module included in your project as well, of course.
//
rat.modules.add("rat.storage.r_storage_firebase",
[
	{ name: "rat.storage.r_storage", processBefore: true },

	"rat.debug.r_console",

],
function (rat)
{
	/** 
	    Firebase storage object
	    @constructor 
	    @extends rat.BasicStorage
	   
 */
	rat.FirebaseStorage = function (address, prefix, userID)
	{
		rat.FirebaseStorage.prototype.parentConstructor.call(this, prefix); //	default init

		//	TODO:  UserID support.
		//		if a userID is supplied (and only if it's supplied), do everything in a subfolder of the storage firebase,
		//		instead of at the top level.  For now, this is unimplemented.
		//	TODO:  also optionally support rat_anon_id like telemetry module does.

		this.ref = null;
		if (typeof (Firebase) !== 'undefined')	//	is firebase even reachable?
		{
			var fullAddress = address;
			if (prefix && prefix !== '')
				fullAddress += "/" + prefix;
			this.ref = new Firebase(fullAddress);
		}
		if (!this.ref)
		{
			//	failed for one reason or another.  error message?  error state?
			rat.console.log("WARNING!  Attempting to use Firebase storage without Firebase");
			return;
		}

		var self = this;
		this.data = void 0;	//	initially undefined.  Later, this will either have data, or be null (meaning there WAS no data)
		this.ref.once('value', function (snap)
		{
			//	grab data
			self.data = snap.val();
			//	tell people we got it
			self._fireOnReady();
		});

	};
	rat.utils.inheritClassFrom(rat.FirebaseStorage, rat.BasicStorage);

	/**
	* @suppress {missingProperties}
	*/
	rat.FirebaseStorage.prototype._internalSetItem = function (key, value)
	{
		if (!this.ref)
			return;
		var ref = this.ref;
		if (key && key !== '')
			ref = ref.child(key);
		ref.set(value);
	};

	/**
	* @suppress {missingProperties}
	*/
	rat.FirebaseStorage.prototype._internalGetItem = function (key)
	{
		//	we already requested data.  Let's hope we have it.
		if (!this.hasData())
			return null;

		if (key && key !== '')
			return this.data[key];
		else
			return this.data;
	};
	/**
	* @suppress {missingProperties}
	*/
	rat.FirebaseStorage.prototype.remove = function (key)
	{
		//	remove data at this location.
		var ref = this.ref;
		if (key && key !== '')
			ref = ref.child(key);
		ref.remove();
		this.data = null;
	};

	/** @suppress {checkTypes} */
	rat.FirebaseStorage.prototype.hasData = function ()
	{
		//	have I ever gotten my data from firebase?
		if (typeof (this.data) === 'undefined')
			return false;
		return true;
	};

});
//--------------------------------------------------------------------------------------------------
//
//	Simple matchmaking service
//
/*

	This module supports listing games, joining games, and hosting new listed games.
		(no game-specific details, just the listing/delisting/browsing service)
		
	We help a player connect directly to a game.  We don't have any concept of a lobby here.
	We assume join on the fly for everyone.
	
	We track the connection of the current host of a game.  If the host disconnects, we delist a game.
		This is primarily to avoid having old dead games listed forever.
		This means that during host migration (from host disconnect) we have to explicitly have the new
			host re-list the game.
		But I've thought through this several times, and I think delisting on disconnect is good, right?
		I'm second-guessing again:
			Should I use up a firebase connection for maintaining an active game?
			We shouldn't see the matchmaking service as the only way to access a game...
			If somebody remembers the unique address of a game, they can certainly reconnect.
			And we expect players to save their game and re-host it, if they want to play another session days later.
	
	We can track and show some info about each match, if host is using r_sync or something like it. (optional)
		Currently, this means host name and player count.
	
	We could potentially implement player count limits, but the actual host of the game needs to be the final say there,
		since we might have out of date info here, or two players might try to connect in quick succession or something.
		
	Host Migration support
		This is generally implemented elsewhere (see r_sync), but we need to keep it in mind in a few
		places.

	matchmaking todo:
		* clean up old dead games in the matchmaking list
			rare case, but still happens, e.g. with crash/interruption in setup..
			Implementation:
				host should update matchmaking info regularly with a timestamp?
				track when it was created?  If it's a day old, kill it?
				automatically check game player list activity?
				At the very least, we currently know player count if the host uses r_sync...
					If that's all correct, we should kill 0-player games.
				Better would be to kill games with no players who have reported active recently.
		* clean up old dead games in the game list (is that our job?  It needs to be somebody's!)

		* private / searchable by name?
		* support max player limit?
		* lock/passwords
		* filters (e.g. don't show full games, sort by ping, etc.)
		* security, though that gets tricky with host migration.
		
		* standard matchmaking list ui element for ease of use in new games?
		
*/

rat.modules.add("rat.live.r_match",
[
	{ name: "rat.live.r_firebase", processBefore: true },
	{ name: "rat.os.r_system", processBefore: true },
	"rat.debug.r_console",
],
function (rat) {
	if (!rat.live)
		rat.live = {};
	/** 
	    Namespace for matchmaking functionality
	    @namespace
	   
 */
	rat.live.match = {};
	var match = rat.live.match;	//	convenient local reference
	
	//	init
	//	project root specifies top-level directory to start at on served database.  example 'rtest'
	//	useFirebase is optional - generally, just leave it blank, or see documentation for initializing multiple firebase apps
	match.init = function(projectRoot, useFirebase)
	{
		rat.console.log("matchmaking init");
		
		match.connection = new rat.live.firebase.Connection(projectRoot, useFirebase);
		
		match.mGamesRef = match.connection.ref('match/');	//	list of hosted games (sessions)
		
		//	Maybe read something once, at startup, so we know we're getting data...?
	};
	
	//	disconnect from matchmaking server
	//	(currently, doesn't really disconnect from firebase, just cleans up my connection objects)
	match.shutDown = function()
	{
		rat.console.log("matchmaking shut down");
		match.connection.close();
		match.connection = null;
	};
	
	//	Is the system up and running correctly?  Did we finish init and get some data at some point?
	match.isReady = function()
	{
		return match.connection.isReady();
		//if (!match.mGamesRef)
		//	return false;
	};
	//	Is the system connected right now?  (this can go up and down)
	match.isConnected = function()
	{
		return match.connection.connected;
	};
	
	//---------------
	//	Matchmaking
	//	todo: move to another module
	
	//	watch game list
	//	todo: support filter
	match.watchGameList = function(callback, options)
	{
		match.mGamesRef.on("value", function(snap) {
			
			//	turn it into an array
			var val = snap.val();
			//	may be empty, of course
			
			var gameList = [];	//	list version, temporary to this function
			match.gameListVal = val;	//	hash version
			
			var count = 0;
			for (var prop in val)
			{
				if (val.hasOwnProperty(prop))
				{
					count++;
					if (count > 20)	//	hack - we've got a lot of crazy listed stuff on matchmaking server sometimes
						break;
					
					val[prop].id = prop;	//	should be unique ID assigned by firebase
					gameList.push(val[prop]);
					
					match.augmentPlayerInfo(prop);
					match.augmentAgeInfo(prop);
					
				}
			}
			//	Note: if game has one selected or something, it can use the "id" property
			//	to uniquely identify it.
			callback(gameList);
		});
	};
	match.unwatchGameList = function()
	{
		match.mGamesRef.off();
	};
	
	//	optionally(?) listen to player list changes in the hosted game itself,
	//	assuming the game is managed with r_sync or something like it.
	//	This lets us track number of players, and maybe other things...
	//	Lots of assumptions here, including the idea that games are hosted on the same server,
	//	in a known location.
	match.augmentPlayerInfo = function(gameID)
	{		
		if (!match.gameListVal[gameID])
			return;
		
		var ref = match.connection.ref('games/' + gameID + '/_players/')
		ref.on('value', function(plSnap) {
			
			//	in the mean time, it went away
			if (!match.gameListVal[gameID])
				return;
			
			var pCount = 0;
			var pHostName = null;
			var pList = plSnap.val();
			if (pList)
			{
				//	process the list a bit - only remember count and host name,
				//	and watch for active players?  hmm...
				for (var p in pList)
				{
					pCount++;
					if (pList[p].isHost)
						pHostName = pList[p].name;
				}
			}
			match.gameListVal[gameID].activePlayerCount = pCount;
			match.gameListVal[gameID].activeHostName = pHostName;
		});	
		
	};
	
	match.augmentAgeInfo = function(gameID)
	{
		//	optionally (?) kill old games.
		var ref2 = match.connection.ref('games/' + gameID + '/_info/')
		ref2.on('value', function(infoSnap) {
			//	in the mean time, it went away
			if (!match.gameListVal[gameID])
				return;
				
			var info = infoSnap.val();
			var killMe = false;
			if (info && info.hostHeartbeat)
			{
				var d = new Date();
				var curTime = d.getTime();
				var deltaTime = curTime - info.hostHeartbeat;
				//	miliseconds...
				if (deltaTime > 1000 * 60 * 10)	//	10 minutes
				{
					killMe = true;
					
					//console.log(" M Check " + d.toUTCString() + " : " + curTime);
					//console.log(" M Against " + info.hostHeartbeatString + " : " + info.hostHeartbeat);
				}
				match.gameListVal[gameID].age = deltaTime;
			}
			//if (killMe)
			//{
			//	rat.console.log("Matchmaking kill old game: " + gameID);
			//}
		});
	};
	
	//	UTIL:
	//	delete games on server past a certain age (in ms)
	match.deleteOldGames = function(age)
	{
		if (!age)
			age = 1000 * 60 * 20;
		
		console.log("checking to delete with age " + age/(1000*60) + " minutes");
		var ref = match.connection.ref('games/');
		ref.once('value', function(snap) {
			var val = snap.val();
			if (val)
			{
				var count = 0;
				var deleteCount = 0;
				for (var gameID in val)
				{
					var theGame = val[gameID];
					var d = new Date();
					var curTime = d.getTime();
					var deltaTime = curTime - theGame._info.hostHeartbeat;
					//	miliseconds...
					if (deltaTime > 1000 * 60 * 10)	//	10 minutes
					{
						rat.console.log("M KILL OLD " + gameID);
						ref.child(gameID).remove();
						deleteCount++;
					}
					count++;
				}
				rat.console.log("Checked " + count + " games for age, deleted " + deleteCount);
			}
		});	
	};
	
	//	UTIL:
	//	delete games on server with no players
	match.deleteEmptyGames = function()
	{
		console.log("checking to delete with no players.");
		var ref = match.connection.ref('games/');
		ref.once('value', function(snap) {
			var val = snap.val();
			if (val)
			{
				var count = 0;
				var deleteCount = 0;
				for (var gameID in val)
				{
					var theGame = val[gameID];
					if (!theGame._players || theGame._players === {})
					{
						rat.console.log("M KILL EMPTY " + gameID);
						ref.child(gameID).remove();
						deleteCount++;
					}
					count++;
				}
				rat.console.log("Checked " + count + " games for empty, deleted " + deleteCount);
			}
		});	
	};
	
	//	Inform the matchmaking service that we're hosting a new game.
	//	This creates a new entry in the game list and assigns a game ID which the game is expected to use!
	//	"config" will eventually be stuff like private, searchable, password, player max, description, whatever...
	//	Maybe the callback here is not always useful?
	match.hostGame = function(name, config, joinCallback, errorCallback)
	{
		//	create the new game.
		var newRef = match.mGamesRef.push();
		//		todo: race condition here of disconnecting before remove is called below?
		//		that'd be a weird case, and we kinda need a clean-up-old-games system anyway, so don't worry about it now.
		
		//	new id is newRef.key.  Use that to host with ID below.
		match.hostGameWithID(newRef.key, name, config, joinCallback, errorCallback);
		
		return newRef.key;
	};
	
	//	Inform the matchmaking service that this game, with this ID, is being hosted.
	//	This will replace any existing info about that game, if any.
	//	This is nice for host migration - new person steps up and claims responsibility for this game.
	//	TODO: use whatever info (e.g. config) is already there instead of overwriting?
	//	Be careful that "id" is unique.
	//	*generally*, you should have gotten that ID from this matchmaking system in the first place,
	//	by hosting or joining a game earlier.
	//	But it's possible to just make up an id (somehow unique) and use that, if you want.
	match.hostGameWithID = function(id, name, config, joinCallback, errorCallback)
	{
		if (!config)
		{
			config = {};
			//	TODO: use whatever was already there, if there was something there! (how to get that first?)
			//	or just don't specify it if it's already there, or use a transaction or something...
			//	For now, we assume we're going to overwrite the config on any host effort...
		}
		var theRef = match.mGamesRef.child(id);
		
		//	If this node disconnects, delist the game.
		//	This helps keep the game list cleaned up.
		//	Host migration (in r_sync system) relists the game if desired.
		theRef.onDisconnect().remove();
		
		//	set up initial data
		theRef.set({
			name : name,
			config : config,
		});
		
		//	join callback (mostly just passes back all the info we were handed.  Not useful?)
		if (joinCallback)
		{
			var joinGameInfo = {
				isHost : true,
				name : name,
				id : id,
				config : config,
			};
			joinCallback(joinGameInfo);
		}
	};
	
	//	I'd like to join this game.  Make sure it's OK,
	//	and then call my callback function.
	//	Return true if the game exists, false if not.
	//	Again, this callback may not be useful.  All the client really needs
	//	is the unique game ID that they obviously already have.
	match.joinGame = function(id, joinCallback, errorCallback)
	{
		//	confirm that you can....
		
		if (!this.gameListVal)
			return false;	//	todo: call error
		
		var gameEntry = this.gameListVal[id];
		if (!gameEntry)
			return false;	//	todo: call error
		
		if (joinCallback)
		{
			var joinGameInfo = rat.utils.copyObject(gameEntry, true);
			joinGameInfo.id = id;
			joinGameInfo.isHost = false;
			joinCallback(joinGameInfo);
		}
		
		return true;
	};
});
//--------------------------------------------------------------------------------------------------
//
//	top-level storage layer
//
//	The idea is to wrap access to a few storage options...
//	Use a single common API,
//	and be able to prototype and test things like win8 storage while developing in a browser
//
//	Usage:
//		var storage = rat.storage.getStorage(rat.storage.permanentLocal);
//		storage.setItem("hey", 12);
//		var x = storage.getItem("hey");
//		storage.setObject("frank", { age: 12, friends: 3 });
//
//	todo:  move system-specific implementation (e.g. win8) to other modules!
//	todo:  Support encryption and decryption on all things, since they're all just strings.
//		should be pretty easy, actually...  just xor or something.  See drawpig...
//		maybe add a few weird chars in for kicks, like before and after, and strip them off later, so single-char values are not obvious
//	todo:  Support encryption/decryption of key as well!
//	todo:  Since this will be open-source, let the client app set the encryption parameters.

//	storage namespace
rat.modules.add("rat.storage.r_storage",
[
	{ name: "rat.utils.r_utils", processBefore: true },
	{ name: "rat.debug.r_console", processBefore: true }, // so i can use rat.console.registerCommand

	{ name: "rat.storage.r_storage_xbo", platform: "xbox" },
	{ name: "rat.storage.r_storage_firebase"/*, platform: void 0*/ }, // How to get this one to load.
	"rat.os.r_system",
	"rat.os.r_user"
],
function (rat)
{
	rat.storage = {
		storageObjects: {
			byUser: {}, // Each user also has a byType
			byType: {}
		}
	};

	/**
	 * Debug function to clear any held data (all of it! anything we have access to, including
	 * values in localStorage that belong to other games)
	 * @suppress {missingProperties}
	 */
	rat.storage.clear = function ()
	{
		function clearData(store)
		{
			var fields = store.values;
			for (var key in fields)
			{
				if (fields.hasOwnProperty(key))
				{
					store.values.remove(key);
				}
			}
		}

		if (window.Windows !== void 0)
		{
			clearData(window.Windows.Storage.ApplicationData.current.localSettings);
			clearData(window.Windows.Storage.ApplicationData.current.roamingSettings);
		}

		if (rat.system.has.xbox)
			rat.storage.XboxOne.clear();

		var sysLocalStorage = rat.system.getLocalStorage();
		if (sysLocalStorage)
		{
			if (sysLocalStorage.clear)
				sysLocalStorage.clear();
			else if (rat.system.has.Wraith)
				rat.console.log("To clear user settings under wraith, use the wraith cheat \"resetPrefs\"");
		}
		
		if (document.cookie)
		{
			document.cookie = "FakeStorage=; expires=Thu, 01 Jan 1970 00:00:00 UTC";
		}
	};

	//	return a new reference to the given storage system
	/**
	 * @param {number} storeType
	 * @param {Object=} user
	 * @suppress {checkTypes}
	 */
	rat.storage.getStorage = function (storeType, userID)
	{
		//	Only have one storage per type per user ever
		// we don't want to kick off the startup calls each time we look for a storage object, so save it off
		if (userID !== void 0 && userID.id !== void 0)
			userID = userID.id;
		else
			userID = userID || "";
		var storageObjects = rat.storage.storageObjects.byType;
		if (userID)
		{
			rat.storage.storageObjects.byUser[userID] = rat.storage.storageObjects.byUser[userID] || {};
			storageObjects = rat.storage.storageObjects.byUser[userID];
		}
		if (storageObjects && storageObjects[storeType])
			return storageObjects[storeType];

		//	create the proper object depending on requested service and host system.
		//	Each type of object we instantiate supports all the same API.  See below.

		//	for xbox, send this call off to xbox specific code in another module.
		if (rat.system.has.xbox)
			storageObjects[storeType] = rat.storage.XboxOne.getStorage(storeType, userID);

		else if (rat.system.has.realWindows8)
		{
			if (storeType === rat.storage.permanentLocal)
				storageObjects[storeType] = new rat.Win8AppStorage('', true);
			else if (storeType === rat.storage.permanentRoaming)
				storageObjects[storeType] = new rat.Win8AppStorage('', false);
			else if (storeType === rat.storage.suspendLocal)
				storageObjects[storeType] = new rat.Win8SessionStorage('');
		}
		else
		{
			var Ctor = rat.LocalStore;
			if( !rat.system.has.localStorage && document.cookie != void 0 )
				Ctor = rat.LocalCookieStore;
			if (storeType === rat.storage.permanentLocal)
				storageObjects[storeType] = new Ctor('');
			else if (storeType === rat.storage.suspendLocal)
				storageObjects[storeType] = new Ctor('_sus_');
		}

		return storageObjects[storeType];
	};

	rat.storage.permanentLocal = 1;	//	e.g. local store - store on local device
	rat.storage.permanentRoaming = 2;	//	store in cloud
	rat.storage.permanentServer = 3;	//	store on our game server per user
	rat.storage.suspendLocal = 4;		//	temp during suspend
	
	//	make storage clear command available in console
	rat.console.registerCommand("clearStorage", function (cmd, args)
	{
		rat.storage.clear();
	}, ["clearstore"]);
	
	//--------------------- generic implementation - base class -----------------------------

	/** 
	    BasicStorage for basic storage class
	    @constructor 
 */
	rat.BasicStorage = function (prefix)
	{
		this.prefix = prefix || "";
		this._onReady = [];	//	Functions to fire when the storage is ready
		this.defaultData = void 0;
	};

	/**  Add a new on ready function
 */
	rat.BasicStorage.prototype.onReady = function (func, ctx)
	{
		if (!func)
			return;
		if (this.hasData())
			func.call(ctx);
		else
			this._onReady.push({ func: func, ctx: ctx });
	};

	//	Fire all of the registered onReady functions
	rat.BasicStorage.prototype._fireOnReady = function ()
	{
		var list = this._onReady;
		this._onReady = [];
		var func, ctx;
		for (var index = 0; index !== list.length; ++index)
		{
			func = list[index].func;
			ctx = list[index].ctx;
			func.call(ctx);
		}
	};

	//	set prefix for all accesses
	rat.BasicStorage.prototype.setPrefix = function (prefix)
	{
		this.prefix = prefix || "";
	};

	//	set object by packing it up and setting a single value
	rat.BasicStorage.prototype.setObject = function (key, value)
	{
		this.setItem(key, JSON.stringify(value));	//	don't use prefix here - setItem will do that.
	};

	//	get object by unpacking value, if it's there
	rat.BasicStorage.prototype.getObject = function (key)
	{
		var thing = this.getItem(key);	//	don't use prefix here - getItem will do that
		//rat.console.log("Got storage value for " + key);
		if (thing === "{")
			return {};
		if (typeof (thing) !== "string" || thing === "")
			return thing;
		return JSON.parse(thing);
	};

	// Empty save function  Platforms which support this can implement it.
	rat.BasicStorage.prototype.save = function (func, ctx)
	{
		if (this.hasData())
		{
			if (this._internalSave)
				this._internalSave(func, ctx);
			else
			{
				if (func)
					func.call(ctx);
			}
		}
		else
		{
			this.onReady(this.save.bind(this, func, ctx), void 0);
		}
	};

	// Empty hasData function  Platforms which support this can implement it.
	rat.BasicStorage.prototype.hasData = function ()
	{
		return true;
	};

	/** 	set our default data - if we desire - currently unused
 */
	rat.BasicStorage.prototype.setDefaults = function (defaults)
	{
		this.defaultData = defaults;
	};

	/**  Set a value through this storage.
 */
	rat.BasicStorage.prototype.setItem = function (key, value)
	{
		if (typeof (value) !== "string")
			value = JSON.stringify(value);

		if (key === "save")
		{
			rat.console.log("ERROR! You cannot save items to local storage with the key 'save'.  That keyword is reserved!");
			return;
		}

		if (!this.hasData())
		{
			rat.console.log("WARNING: Setting item on storage object that is not ready.  Set delayed...");
			this.onReady(this.setItem.bind(this, key, value), void 0);
			return;
		}

		this._internalSetItem(key, value);
	};

	//	Each of my subclasses should define a _internalSetItem function
	rat.BasicStorage.prototype._internalSetItem = void 0;
	//function( key, value )
	//{
	//};

	//	Each of my subclasses should define an _internalGetItem function
	rat.BasicStorage.prototype._internalGetItem = void 0;
	//function( key )
	//{
	//};

	//	Get a value out
	rat.BasicStorage.prototype.getItem = function (key)
	{
		if (!this.hasData())
		{
			rat.console.log("ERROR! Attempting to ready storage when it is not ready!");
			return void 0;
		}
		if (key === "save")
		{
			rat.console.log("ERROR! You cannot load items from local storage with the key 'save'.  That keyword is reserved!");
			return void 0;
		}
		var val = this._internalGetItem(key);
		if (val === void 0 && this.defaultData && this.defaultData[key] !== void 0)
			val = this.defaultData.key;
		return val;
	};

	//--------------------- local store implementation -----------------------------

	/** 
	    Local Store
	    @constructor 
	    @extends rat.BasicStorage
	   
 */
	rat.LocalStore = function (prefix)
	{
		rat.LocalStore.prototype.parentConstructor.call(this, prefix); //	default init
		
		this._fireOnReady();
	};
	rat.utils.inheritClassFrom(rat.LocalStore, rat.BasicStorage);

	function getActiveUserID()
	{
		if (rat.user)
			return rat.user.getActiveUserID() || void 0;
		return void 0;
	}

	//	just map the simple calls
	// We suppress checkTypes because Wraith does take three args even if windows only takes 2
	/** @suppress {checkTypes} */
	rat.LocalStore.prototype._internalSetItem = function (key, value)
	{
		if (rat.system.has.localStorage)
			rat.system.localStorageObject.setItem(this.prefix + key, value, getActiveUserID());
	};
	// We suppress checkTypes because Wraith does take three args even if windows only takes 2
	/** @suppress {checkTypes} */
	rat.LocalStore.prototype._internalGetItem = function (key)
	{
		var res = null;
		if (rat.system.has.localStorage)
			res = rat.system.localStorageObject.getItem(this.prefix + key, getActiveUserID());
		return res;
	};
	// We suppress checkTypes because Wraith does take three args even if windows only takes 2
	/** @suppress {checkTypes} */
	rat.LocalStore.prototype.remove = function (key)
	{
		if (rat.system.has.localStorage)
			rat.system.localStorageObject.removeItem(this.prefix + key, getActiveUserID());
	};
	// We suppress checkTypes because Wraith does take three args even if windows only takes 2
	/** @suppress {checkTypes} */
	rat.LocalStore.prototype.hasData = function ()
	{
		if (rat.system.localStorageObject && rat.system.localStorageObject.hasData)
			return rat.system.localStorageObject.hasData(getActiveUserID());
		return true;
	};
	// We suppress missingProperties save may exist (as it does under Wraith)
	/** @suppress {missingProperties} */
	rat.LocalStore.prototype._internalSave = function (func, ctx)
	{
		var res;
		if (rat.system.localStorageObject && rat.system.localStorageObject.save && typeof (rat.system.localStorageObject.save) === "function")
		{
			rat.console.log("Saving storage.");
			res = rat.system.localStorageObject.save(getActiveUserID());
			rat.console.log("...Done");
		}
		if (func)
			func.call(ctx);
		return res;
	};
	
	//--------------------- Cookie version for when we don't have local storage -----------------------------

	/** 
	    Local Cookie Storage
	    @constructor 
	    @extends rat.BasicStorage
	   
 */
	rat.LocalCookieStore = function (prefix)
	{
		rat.LocalCookieStore.prototype.parentConstructor.call(this, prefix); //	default init

		//	Record the data that we know is in the cookie
		this.cookieData = {};
		
		//	Get the current cookie data
		var str = document.cookie || "";
		var propList = str.split(";");
		for( var i = 0; i < propList.length; ++i )
		{
			var prop = propList[i].trim();
			var name, value;
			var equalAt = prop.indexOf("=");
			if( equalAt != -1 )
			{
				name = prop.substr( 0, equalAt ).trim();
				value = prop.substr( equalAt + 1 ).trim();
			}
			else
			{
				name = prop;
				value = "{}";
			}
			
			if( name == "FakeStorage" )
			{
				try
				{
					this.cookieData = JSON.parse(value);
				}
				catch(err)
				{
					rat.console.log("WARNING: Unable to parse save data from cookie data");
					this.cookieData = {};
				}
				break;
			}
		}
		
		//	Ready
		this._fireOnReady();
	};
	rat.utils.inheritClassFrom(rat.LocalCookieStore, rat.BasicStorage);

	//	just map the simple calls
	// We suppress checkTypes because Wraith does take three args even if windows only takes 2
	/** @suppress {checkTypes} */
	rat.LocalCookieStore.prototype._internalSetItem = function (key, value)
	{
		if( this.prefix.length > 0 )
			key = this.prefix + key;
		this.cookieData[this.prefix + key] = value;
		this._internalSave();
	};
	// We suppress checkTypes because Wraith does take three args even if windows only takes 2
	/** @suppress {checkTypes} */
	rat.LocalCookieStore.prototype._internalGetItem = function (key)
	{
		if( this.prefix.length > 0 )
			key = this.prefix + key;
		return this.cookieData[key];
	};
	// We suppress checkTypes because Wraith does take three args even if windows only takes 2
	/** @suppress {checkTypes} */
	rat.LocalCookieStore.prototype.remove = function (key)
	{
		if( this.prefix.length > 0 )
			key = this.prefix + key;
		delete this.cookieData[key];
		this._internalSave();
	};
	// We suppress checkTypes because Wraith does take three args even if windows only takes 2
	/** @suppress {checkTypes} */
	rat.LocalCookieStore.prototype.hasData = function ()
	{
		return true;
	};
	// We suppress missingProperties save may exist (as it does under Wraith)
	/** @suppress {missingProperties} */
	rat.LocalCookieStore.prototype._internalSave = function (func, ctx)
	{
		rat.console.log("Saving storage.");
		document.cookie = "FakeStorage=; expires=Thu, 01 Jan 1970 00:00:00 UTC";
		document.cookie = "FakeStorage="+ JSON.stringify(this.cookieData) + ";";
		rat.console.log("...Done");
		if (func)
			func.call(ctx);
		return;
	};

	//--------------------- win8 implementation -----------------------------
	/** @todo	move to separate module!
 */

	/** 
	    Win8SessionStorage for winjs session storage, which is useful for suspend/resume!
	    @constructor 
	    @extends rat.BasicStorage
	   
 */
	rat.Win8SessionStorage = function (prefix)
	{
		rat.Win8SessionStorage.prototype.parentConstructor.call(this, prefix); //	default init
	};
	rat.utils.inheritClassFrom(rat.Win8SessionStorage, rat.BasicStorage);

	/**
	 * Wrapper around getting the session state from the WinJS.Appliation object
	 */
	rat.Win8SessionStorage.prototype.getSessionStateObj = function ()
	{
		if (window.WinJS && window.WinJS.Application)
			return window.WinJS.Application.sessionState;
		else
			return {};
	};


	/**
	* @suppress {missingProperties}
	*/
	rat.Win8SessionStorage.prototype._internalSetItem = function (key, value)
	{
		this.getSessionStateObj()[this.prefix + key] = value;
	};

	/**
	* @suppress {missingProperties}
	*/
	rat.Win8SessionStorage.prototype._internalGetItem = function (key)
	{
		return this.getSessionStateObj()[this.prefix + key];
	};
	/**
	* @suppress {missingProperties}
	*/
	rat.Win8SessionStorage.prototype.remove = function (key)
	{
		this.getSessionStateObj()[this.prefix + key] = null;
	};


	/** 
	    WinJS application storage (local or cloud)
	    @constructor
	    @extends rat.BasicStorage
	    @suppress {missingProperties}
	   
 */
	rat.Win8AppStorage = function (prefix, useLocal)
	{
		rat.Win8AppStorage.prototype.parentConstructor.call(this, prefix); //	default init
		if (useLocal)
			this.settings = window.Windows.Storage.ApplicationData.current.localSettings;
		else
			this.settings = window.Windows.Storage.ApplicationData.current.roamingSettings;
	};
	rat.utils.inheritClassFrom(rat.Win8AppStorage, rat.BasicStorage);

	/**
	* @suppress {missingProperties}
	*/
	rat.Win8AppStorage.prototype._internalSetItem = function (key, value)
	{
		this.settings.values[this.prefix + key] = value;
	};
	/**
	* @suppress {missingProperties}
	*/
	rat.Win8AppStorage.prototype._internalGetItem = function (key)
	{
		return this.settings.values[this.prefix + key];
	};
	/**
	* @suppress {missingProperties}
	*/
	rat.Win8AppStorage.prototype.remove = function (key)
	{
		this.settings.values.remove(this.prefix + key);
	};

});
//--------------------------------------------------------------------------------------------------
//
//	Wrapper around saving a file directly to disk (or asking the user to, which sometimes we must)
//
rat.modules.add("rat.storage.r_file_access",
[
	{ name: "rat.os.r_system", processBefore: true },
	{ name: "rat.utils.r_utils", processBefore: true },
],
function (rat)
{
	rat.fileAccess = {
		
		//	Ask the user if they want to save a file
		save: function(data, fileName, onDone)
		{
			if( typeof(Blob) === "undefined" )
			{
				rat.console.log( "Unable to save.   Not supported" );
				if( onDone )
					onDone();
				return;
			}
			
			if (typeof(data) !== 'string')
				data = JSON.stringify( data );
			var b = new Blob( [data], {type:"text/plain"} );
			
			if( rat.system.has.IEBrowser && rat.system.has.IEVersion >= 10 )
			{
				window.navigator.msSaveBlob(b, fileName || "file" );
				if( onDone )
					onDone();
				return;
			}
			else
			{
				var lnk = document.createElement('a');
				lnk.href = window.URL.createObjectURL( b );
				lnk.download = fileName;
				lnk.click();
				if( onDone )
					onDone();
				return;
			}
		},
		
		//	Ask the user to find a file on the disk to load
		openFile: function( onSuccess, onError )
		{
			//	NOT wraith compatable.  
			
			var fileSelector = document.createElement('input');
			fileSelector.type = "file";
			document.body.appendChild( fileSelector );
			function onChange() {
				//fileSelector.removeEventListener( "change", onChange, false );
				fileSelector.parentNode.removeChild(fileSelector);
				var fileList = fileSelector.files;
				fileSelector = void 0;
				
				//	Open the file
				var reader = new FileReader();
				var fullData = "";
				reader.onload = function() {
					fullData += reader.result;
				};
				for( var index = 0; index < fileList.length; ++index )
					reader.readAsText( fileList[index] );
				reader.onloadend = function() {
					reader.onload = void 0;
					reader.onloadend = void 0;
					if (reader.error) {
						alert( "error: " + reader.error.message );
						if( onError )
							onError( reader.error );
					}
					else {
						if( onSuccess )
							onSuccess( fullData );
						//alert( "done" );
					}
				};
				
			}
			//fileSelector.addEventListener( "change", onChange, false );
			fileSelector.onchange = onChange;
			fileSelector.click();
		},
		
		//	Ask the user to find a file or file on disk.
		//	Don't load them - just get their info.
		//	This is basically useless in a browser.  We get individual file names,
		//	but not path!
		//	so, this is sort of here as placeholder until this API can also reflect something like
		//	chrome apps.
		selectFile: function( onSuccess, onError, allowMultiple )
		{
			var fileSelector = document.createElement('input');
			fileSelector.type = "file";
			if (allowMultiple !== void 0)
				fileSelector.multiple = allowMultiple;
			document.body.appendChild( fileSelector );
			function onChange() {
				//fileSelector.removeEventListener( "change", onChange, false );
				fileSelector.parentNode.removeChild(fileSelector);
				var fileList = fileSelector.files;
				fileSelector = void 0;
				
				if (onSuccess)
					onSuccess(fileList);
			}
			fileSelector.onchange = onChange;
			fileSelector.click();
			
			//	any way to detect cancel?
		},
	};
});

//--------------------------------------------------------------------------------------------------
/**

Hex Grid support
	
CONCEPTS

	AXIAL COORDINATES
		We use Axial coordinates (q,r):
			
			q increments to the right, and r increments along the down/right diagonal axis.
			
		See http://www.redblobgames.com/grids/hexagons/#coordinates
		or http://3dmdesign.com/development/hexmap-coordinates-the-easy-way
		
		This is not the same as offset coordinates.  Important to understand.
	
	STORAGE and BOUNDS
		We have internally a definition of what grid space is defined, determined when you set the grid up.
		Initially, only a rectangular space is supported, but it'd be easy to add other spaces, like a big hex.
		You can find out if a hex is in valid space by calling inBounds();

IMPLEMENTATION NOTES
	Existing Libraries:
	Why didn't I use an existing hex grid library?
		I looked at several, and they didn't do what I needed, and took the wrong fundamental approach.
		Some are flat-top only, and I wanted pointy-top, or the ability to choose.
		HexLib.js hasn't been updated in a couple of years, but more importantly, it's very DOM-heavy:
			about half the file is implementing detailed event handling and stuff.
			It even has sprite classes baked in.  I don't want any of that.
		I want core hex-grid math, positioning, pathfinding, data mangement, etc.
		Rat can do everything else already.
	
	Reference:
	Super useful resource: http://www.redblobgames.com/grids/hexagons/
	Also interesting: http://keekerdc.com/2011/03/hexagon-grids-coordinate-systems-and-distance-calculations/

	Axial Coordinates:
	When I first started implementing this, I assumed I'd use offset coordinates,
		because that's what most people expect.
		But the more I read about it, the more I became convinced that Axial makes for easier implementation,
		and isn't that hard to understand.  I've been using this Axial system for a year now, since
		first implementing HexGrid, and it's definitely fine.  No big problem to understand, and
		with much easier to understand delta directions when moving from one square to another.
		
		Axial means (in a pointy system) that q increments to the right, and r increments down/right diagonally.
		And let's consider 0,0 the top left of our world, for now.
		(though I think that's flexible now)
		
TO DO:
	
	* test case in rtest for each of the below, which makes it more fun to implement anyway.
	
	[x] generic "for each" system for our defined space
		[x] convert our setAllXX functions to use it.

	[x] pixel to hex

	[x] support for squished hexes, determined at setup.
		
	* pathfinding
		
		[x] shortest path breadth-first
		
		[x] multi-pass split over calls!
			(define how many passes you want to happen, or let the whole search happen in one call)

		[x] support for marking blocking directly in data entries.
		
		* preferred avoidance of something with weights (cost per hex).
			(mark blocking as cost value ... 0 = no block, -1 = full block, other = cost)
		
		* Support A* (not done)
			
		[x] support multiple active pathfinding datasets
			so don't store pathfinding data in hexgrid itself.
			We also support different blocking sets (stored in our data but set/read by user),
			which is important in cases like different players having their own pathfinding objectives/knowledge.

		for pathfinding implementation, see
		http://www.redblobgames.com/pathfinding/a-star/introduction.html
		
	[x] distance
	
lower-pri todo:

	* list of coordinates in a range
		* including factoring in blocking squares

	* line of sight (pretty cool, but do we need it somewhere?)
	
	* copy function (copy one of these hexgrid objects to another.)
		e.g. to store a given state for later reference.
	
	* flat-top style instead of pointy.  Right now, pointy is our only supported style.
	* more optimized debug display?  maybe don't bother.
	
	* define region boundaries from a set of hexes, like culture borders in civ.
		* as paths to be filled/stroked,
		* or as mathematical definitions to be handled in other ways.
	
 */
rat.modules.add("rat.utils.r_hexgrid",
[
	{ name: "rat.math.r_math", processBefore: true },

	"rat.utils.r_utils",
],
function (rat)
{
	//	useful constants or references.
	var SQRT3 = rat.math.sqrt(3);

	/** 
	    Hex Grid 
	    potentially a lot of configuration options, so pass them in an object, instead of confusing list of args
	    @constructor 
	   
 */
	var HexGrid = function (userConfig)
	{
		var config = rat.utils.copyObject(userConfig);

		//	fill in a bunch of defaults
		config.colCount = config.colCount || 1;
		config.rowCount = config.rowCount || 1;
		config.sideSize = config.sideSize || 1;
		config.vScale = config.vScale || 1;	//	squish or stretch (vertically)

		//	keep a copy of that config info for debug, but the more useful values are stored in this.whatever directly.
		//	(we should never refer to config directly again after this init function, for simplicity and clarity)
		this.config = config;

		this.pointyStyle = true;	//	default
		if (typeof (config.style) !== 'undefined')	//	did they want to set a style explicitly?
			this.pointyStyle = (config.style === 'pointy');

		this.size = config.sideSize;

		//	precalculate some common stuff...
		this.vScale = config.vScale;
		this.hexHeight = this.size * 2 * this.vScale;
		this.hexWidth = SQRT3 / 2 * this.size * 2;

		//	specifying columns and rows tells us what kind of rectangular grid we want.
		//	0,0 is upper left, and with pointy style, we end up similar to the
		//	"odd-r" horizontal layout, but with axial coordinates.
		//	We expect the caller to understand this stuff, at least the axial coordinates part,
		//	which is important.

		//	We will eventually support different storage areas, like hex-shaped or triangular-shaped, or something,
		//	but each of these will still need offsets, or something similar to below,
		//	so it shouldn't matter much.

		var id = 0;	//	unique ID per hex - useful for pathfinding, and might be useful in other ways?
		this.data = [];
		for (var y = 0; y < config.rowCount; y++)
		{
			//	note - if we want a hex-shaped grid,
			//		we could store fewer entries here (and an offset) in this sub-array
			var row = [];
			this.data[y] = row;

			//	here's where we have to deal with an axial storage system.
			//	mark starting x offset in this row.
			//	e.g. what value to add to any index to get the real axial q,r coordinates
			//		(or inversely, what value to subtract from axial q to get index)

			row.offset = -(rat.math.floor(y / 2));

			for (var x = 0; x < config.colCount; x++)
			{
				this.data[y][x] = { value: (void 0), blocking: {'main':HexGrid.noBlocking}, debugStyle: (void 0), id: id++ };
			}
		}
	};

	//	standard directions (and indices in following "neighbors" table)
	HexGrid.direction = {
		right: 0,
		upRight: 1,
		upLeft: 2,
		left: 3,
		downLeft: 4,
		downRight: 5,
	};

	//	standard neighbors (in axial coordinates), and other neighbor info
	HexGrid.neighbors = [
		{ delta: { q: +1, r: 0 } },	//	right 1
		{ delta: { q: +1, r: -1 } },	//	upright
		{ delta: { q: 0, r: -1 } },	//	upleft
		{ delta: { q: -1, r: 0 } },	//	left
		{ delta: { q: -1, r: +1 } },	//	downleft
		{ delta: { q: 0, r: +1 } }	//	downright
	];
	//	standard 2-step neighbors (in axial coordinates), and other neighbor info
	HexGrid.neighbors2 = [
		{ delta: { q: +2, r: 0 } },	//	right
		{ delta: { q: +2, r: -1 } },
		{ delta: { q: +2, r: -2 } },
		{ delta: { q: +1, r: -2 } },
		{ delta: { q: 0, r: -2 } },
		{ delta: { q: -1, r: -1 } },
		{ delta: { q: -2, r: 0 } },	//	left
		{ delta: { q: -2, r: +1 } },
		{ delta: { q: -2, r: +2 } },
		{ delta: { q: -1, r: +2 } },	//	left
		{ delta: { q: 0, r: +2 } },
		{ delta: { q: +1, r: +1 } },
	];

	//	todo:  We need a more generic way to define relative sets of hexes
	//	(e.g. step 1 neighbors, step 2 neighbors, both 1 and 2, spiral list, etc.)
	//	and merging those lists into new lists
	//	and turn ALL of those variations into a callback list, optionally factoring in blocking
	//	(see forEachNeigbor below)

	HexGrid.fullBlocking = -1;	//	constant to indicate if a hex is totally blocked (not a cost)
	HexGrid.noBlocking = 0;	//	not blocked at all, no cost.
	//	otherwise, blocking costs can be whatever, e.g. 1, 4, 100...

	//	is this (axial) position valid?  (Is it within the data space we have defined)
	HexGrid.prototype.inBounds = function (q, r)
	{
		if (r < 0 || r >= this.data.length)
			return false;
		var row = this.data[r];
		var dataIndex = q - row.offset;
		if (dataIndex < 0 || dataIndex >= row.length)
			return false;
		return true;
	};

	//	forEachHex utility: for each hex in our defined data space,
	//	call this callback function, passing standard arguments in a block: hexGrid, entry, q, r, value, and userData
	HexGrid.prototype.forEachHex = function (callback, userData)
	{
		//	standard args passed to callback
		var args = {
			hexGrid : this,
			//	entry
			//	q, r
			//	value (will be entry.value)
			userData : userData,
		};
		
		//	walk through every hex
		for (var r = 0; r < this.data.length; r++)
		{
			args.r = r;
			var row = this.data[r];
			for (var x = 0; x < row.length; x++)
			{
				var q = x + row.offset;

				//	new callback approach - pass everything in a single block
				//callback(this, row[x], q, r, row[x].value, userData);
				args.q = q;
				args.entry = row[x];
				args.value = args.entry.value;
				
				callback(args);
			}
		}
	};

	//
	//	Get distance between two hexes.
	//	(doesn't depend on actual hex data, it's just math, so not a method)
	//	So, use it like this: var dist = rat.HexGrid.distance(aq, ar, bq, br);
	HexGrid.distance = function (aq, ar, bq, br)
	{
		return (rat.math.abs(aq - bq)
			+ rat.math.abs(aq + ar - bq - br)
			+ rat.math.abs(ar - br)) / 2;
	};
	//	and a prototype linked version just for convenience
	HexGrid.prototype.distance = HexGrid.distance;
	
	//	is 'b' a neighbor of 'a'?
	HexGrid.isNeighbor = function(aq, ar, bq, br, distance)
	{
		if (!distance)
			distance = 1;
		return (this.distance(aq, ar, bq, br) <= distance);
	};
	//	and a prototype linked version just for convenience
	//	could later check if the positions are in valid space?
	HexGrid.prototype.isNeighbor = HexGrid.isNeighbor;

	//	forEachNeigbor utility: for each hex neighboring this one,
	//	call this callback function, passing these arguments in a block (same as above):
	//		hexGrid, entry, q, r, value, and userData (if supplied)
	HexGrid.prototype.forEachNeighbor = function (sq, sr, callback, userData, avoidBlocking, includeSelf, distance)
	{
		//	standard args passed to callback
		var args = {
			hexGrid : this,
			//	entry
			//	q, r
			//	value (will be entry.value)
			userData : userData,
		};
		
		//	find all neighbors
		var neighbors = this.getNeighbors(sq, sr, avoidBlocking, includeSelf, distance);
		for (var i = 0; i < neighbors.length; i++)
		{
			var nq = neighbors[i].q;
			var nr = neighbors[i].r;
			if (!this.inBounds(nq, nr))
				continue;

			var row = this.data[nr];
			var dataIndex = nq - row.offset;
			//var entry = row[dataIndex];

			//	todo: take return value that says whether to continue?  Maybe we were looking for something, and if we found it,
			//	we may as well abort the loop...
			//callback(this, entry, nq, nr, entry.value, userData);
			args.entry = row[dataIndex];
			args.value = args.entry.value;
			args.q = nq;
			args.r = nr;
			callback(args);
		}
	};

	//	return data storage entry at this location.
	//	INTERNAL function only.  Clients use getDataAt() or getValueAt(), or some equivalent.
	HexGrid.prototype.getEntryAt = function (q, r)
	{
		q = q | 0;
		r = r | 0;
		if (!this.inBounds(q, r))
			return void 0;
		var row = this.data[r];
		var dataIndex = q - row.offset;
		return row[dataIndex];
	};

	//	set value (user data) at this location.
	//	return undefined if location invalid, but we generally expect the caller to know beforehand
	//		what's a valid position (e.g. by calling inBounds() themselves)
	HexGrid.prototype.setValueAt = function (q, r, value)
	{
		var entry = this.getEntryAt(q, r);
		if (!entry)
			return void 0;
		entry.value = value;
		return true;	//	I'm not super attached to this - return something more useful?
	};
	//	our standard concept of "clear" value
	HexGrid.prototype.clearValueAt = function (q, r)
	{
		return this.setValueAt(q, r, void 0);
	};

	//	return value (user data) at this location.
	//	return undefined if location invalid, but we generally expect the caller to know beforehand
	//		what's a valid position.
	HexGrid.prototype.getValueAt = function (q, r)
	{
		var entry = this.getEntryAt(q, r);
		if (!entry)
			return void 0;
		return entry.value;
	};

	//	set all defined grid entries to have this user value.
	HexGrid.prototype.setAllValues = function (value)
	{
		this.forEachHex(function (args)
		{
			//hg, entry, q, r
			args.entry.value = value;
		});
	};

	HexGrid.prototype.clearAllValues = function ()
	{
		this.setAllValues(void 0);
	};

	//	set blocking weight at this location. (for pathfinding)
	//	return undefined if location invalid, but we generally expect the caller to know beforehand
	//		what's a valid position.
	HexGrid.prototype.setBlockingAt = function (q, r, blocking, whichSet)
	{
		var entry = this.getEntryAt(q, r);
		if (!entry)
			return void 0;
		if (blocking === void 0)
			blocking = HexGrid.fullBlocking;
		if (whichSet === void 0)
			whichSet = 'main';

		entry.blocking[whichSet] = blocking;

		return true;	//	I'm not super attached to this - return something more useful?
	};
	//	return blocking weight at this location (pathfinding)
	//	return undefined if location invalid, but we generally expect the caller to know beforehand
	//		what's a valid position.
	HexGrid.prototype.getBlockingAt = function (q, r, whichSet)
	{
		var entry = this.getEntryAt(q, r);
		if (!entry)
			return void 0;
		if (whichSet === void 0)
			whichSet = 'main';
		return entry.blocking[whichSet];
	};

	HexGrid.prototype.clearBlockingAt = function (q, r)
	{
		return this.setBlockingAt(q, r, HexGrid.noBlocking);
	};

	//	need: clearallblocking

	//	set debug style (for debug rendering) at this location
	//	return undefined if location invalid, but we generally expect the caller to know beforehand
	//		what's a valid position.
	HexGrid.prototype.setDebugStyleAt = function (q, r, style)
	{
		var entry = this.getEntryAt(q, r);
		if (!entry)
			return void 0;
		entry.debugStyle = style;
		return true;	//	I'm not super attached to this - return something more useful?
	};
	//	our standard concept of "clear" debug style
	HexGrid.prototype.clearDebugStyleAt = function (q, r)
	{
		return this.setDebugStyleAt(q, r, void 0);
	};

	//	set all defined grid entries to have this debug style
	HexGrid.prototype.setAllDebugStyles = function (style)
	{
		this.forEachHex(function (args)
		{
			args.entry.debugStyle = style;
		});
	};

	HexGrid.prototype.clearAllDebugStyles = function ()
	{
		this.setAllDebugStyles(void 0);
	};

	//	return a list of valid neighbors, in coordinate object form.
	//	do not return neighbors out of bounds.
	//	if requested, don't return any blocking hexes, either.
	//		'avoidBlocking' can be true, in which case main blockset is used, or can be the name of another blocking set to look at
	//		(which is like saying true as well)
	//	resulting list is an array of objects, each of which has (grid-absolute) q,r values.
	HexGrid.prototype.getNeighbors = function (q, r, avoidBlocking, includeSelf, distance)
	{
		//	some default args
		if (avoidBlocking === true)	//	simple true - use main default blocking set
			avoidBlockingSet = 'main';
		else
			avoidBlockingSet = avoidBlocking;	//	use variable as blocking set name
		
		if (includeSelf === void 0)
			includeSelf = false;
		if (distance === void 0)
			distance = 1;

		var neighborList = HexGrid.neighbors;
		//	limited support for a wider radius,
		//	still only grabbing one ring (not inclusive with radius 1, blocking ignored).
		if (distance === 2)
		{
			neighborList = HexGrid.neighbors2;
		}
		var res = [];
		for (var nIndex = 0; nIndex < neighborList.length; nIndex++)
		{
			var nq = q + neighborList[nIndex].delta.q;
			var nr = r + neighborList[nIndex].delta.r;
			var entry = this.getEntryAt(nq, nr);
			if (!entry)
				continue;
			if (avoidBlocking && entry.blocking[avoidBlockingSet] === HexGrid.fullBlocking)
				continue;

			res.push({ q: nq, r: nr });
		}
		if (includeSelf)
		{
			//	todo: factor in blocking again?
			res.push({ q: q, r: r });
		}

		return res;
	};

	//	return true if B is a valid neighbor of A
	//	optionally factor in blocking, and optionally allow the values to be the same (includeSelf)
	HexGrid.prototype.isValidNeighbor = function (aq, ar, bq, br, avoidBlocking, includeSelf)
	{
		if (aq === bq && ar === br)	//	first check same location case
			return !!includeSelf;

		if (avoidBlocking === true)	//	simple true - use main default blocking set
			avoidBlockingSet = 'main';
		else
			avoidBlockingSet = avoidBlocking;	//	use variable as blocking set name
		
		for (var nIndex = 0; nIndex < HexGrid.neighbors.length; nIndex++)
		{
			var nq = aq + HexGrid.neighbors[nIndex].delta.q;
			var nr = ar + HexGrid.neighbors[nIndex].delta.r;
			if (nq === bq && nr === br)
			{
				var entry = this.getEntryAt(nq, nr);
				if (!entry)
					return false;
				if (avoidBlocking && entry.blocking[avoidBlockingSet] === HexGrid.fullBlocking)
					return false;

				return true;
			}
		}
		return false;
	};

	//	utility to merge one list of hexes into another list of hexes, ignoring duplicate locations,
	//	which is entirely determined by q,r values.
	HexGrid.prototype.mergeHexList = function (mainList, newList)
	{
		for (var i = 0; i < newList.length; i++)
		{
			var found = false;
			for (var mIndex = 0; mIndex < mainList.length; mIndex++)
			{
				if (mainList[mIndex].q === newList[i].q && mainList[mIndex].r === newList[i].r)
				{
					found = true;
					break;
				}
			}
			if (!found)
				mainList.push(newList[i]);
		}
		return mainList;
	};

	//	give back pixel position (center) from this hex position, factoring in hex size.
	//	this returns the pixel position of the center of the requested hex.
	//	This function works OK with fractional q/r values, unlike other hexgrid functions.
	HexGrid.prototype.axialToPixelPos = function (q, r)
	{
		if (this.pointyStyle)
		{
			return {
				//	todo: use precalculated this.hexHeight, this.hexWidth instead of some of this?
				x: this.size * SQRT3 * (q + r / 2),
				y: this.size * this.vScale * 3 / 2 * r
			};
		} else
		{
			return {
				x: this.size * 3 / 2 * q,
				y: this.size * this.vScale * SQRT3 * (r + q / 2)
			};
		}
	};
	HexGrid.prototype.posToPixelPos = HexGrid.prototype.axialToPixelPos;	//	alternate name

	//	given pixel coordinates, return hex grid coordinates.
	//	note that this may return coordinates outside our defined data space.
	//		that's currently considered fine.
	//		caller should just be sure to check inBounds() before using that hex.
	HexGrid.prototype.pixelPosToHex = function (x, y)
	{
		//	from http://www.redblobgames.com/grids/hexagons/#comment-1063818420
		//	This works by magic, I guess.

		//	he had:
		//	x = (x - this.size) / this.hexWidth;
		//	var t1 = y / (this.hexHeight/2);

		//	but that was too far up/left.  Not factoring in centering of square, I guess?  confusing...
		//	Anyway, to match hex-to-pixel code above...
		//	and adapting for our vScale support...
		x = x / this.hexWidth;	//	this basically normalizes x
		var t1 = (y + this.size * this.vScale) / (this.hexHeight / 2);	//	and y is normalized along with other stuff?

		//	without vScale:
		//x = x / this.hexWidth;	//	this basically normalizes x
		//var t1 = (y + this.size) / (this.hexHeight/2);	//	and y is normalized along with other stuff?

		var t2 = rat.math.floor(x + t1);
		var r = rat.math.floor((rat.math.floor(t1 - x) + t2) / 3);
		var q = rat.math.floor((rat.math.floor(2 * x + 1) + t2) / 3) - r;

		return { q: q, r: r };

		//	So, that's working.  Doesn't factor in non-pointy style, though, right?
		//	Old code for reference:

		/*
		//	first find approximate axial coordinates, and then find closest hex.
		//	this is following http://www.redblobgames.com/grids/hexagons/#pixel-to-hex
		
		var q;
		var r;
		
		if (this.pointyStyle)
		{
			q = (x * SQRT3/3 - y / 3) / this.size;
			r = y * 2/3 / this.size;
		} else {
			q = x * 2/3 / this.size;
			r = (y * SQRT3/3 - x / 3) / this.size;
		}
		
		//	now we have real-number q and r values...  need integers.
		//	Hmm...  use hex rounding, etc.
		
		return {q:1, r:1};
		*/
	};

	//
	//	sort this list of hexes by nearest distance to a destination, in euclidian distance terms.
	//	This is mostly useful for pathfinding.  See below.
	//	Note that this adds an extra "dist2" property to each entry in the list.  Hopefully this doesn't cause anyone problems.
	HexGrid.prototype.sortListByXYDistance = function (nList, endQ, endR)
	{
		var endPos = this.axialToPixelPos(endQ, endR);

		for (var i = 0; i < nList.length; i++)
		{
			var pos = this.axialToPixelPos(nList[i].q, nList[i].r);
			var dx = pos.x - endPos.x;
			var dy = pos.y - endPos.y;
			nList[i].dist2 = dx * dx + dy * dy;
		}

		function comp(a, b)
		{
			return a.dist2 - b.dist2;
		};

		return nList.sort(comp);

	};

	//	Pathfinding
	//	(todo: move to a more generic module that works on things like square grids and nodegraphs,
	//		and provide an API here for the key hex-grid things needed)
	//
	//	find a path from one point to another.
	//	pathInfo controls how we behave and also stores data from previous passes on the same path.
	//	You can have multiple pathfinding actions in progress, if you just keep their pathInfo around.
	//	returns true if complete, otherwise false (meaning we're still working on it)
	//
	//	Here's the minimal data you need to set in pathInfo:
	//		pathInfo.start = start location
	//		pathInfo.end = end location
	//
	//	By default, this does the whole search at once.
	//	If you specify a "cycles" value, we'll only perform X cycles
	//	call this function again with the same pathInfo to perform more cycles.
	//
	//	To force calculations to restart when half done, set pathInfo.done to true.
	//
	//	for reference on these algorithms, see http://www.redblobgames.com/pathfinding/a-star/introduction.html
	//	and see notes above.
	HexGrid.prototype.findPath = function (pathInfo)
	{
		//	fill in a bunch of defaults
		pathInfo.cycles = pathInfo.cycles || -1;	//	if not specified, spend as many cycles as needed

		//	initialize stuff if this is our first time.
		if (!pathInfo.frontier || pathInfo.done)
		{
			pathInfo.path = [];	//	will hold final path

			if (!pathInfo.settings)	//	no user-provided settings, so use defaults.
				pathInfo.settings = {};

			pathInfo.done = false;	//	flags when we're done searching
			pathInfo.valid = false;	//	marks whether we found a path

			var startEntry = this.getEntryAt(pathInfo.start.q, pathInfo.start.r);
			if (!startEntry)	//	no such start square
			{
				pathInfo.valid = false;	//	not a valid path
				pathInfo.done = true;	//	we're done working
				//	todo : set some kind of error flag so they know *why* it failed.
				return true;	//	done (but bogus result).
			}
			pathInfo.startID = startEntry.id;

			//	handle the case where we're already where we're going, so we can be a bit more optimal below.
			if (pathInfo.start.q === pathInfo.end.q && pathInfo.start.r === pathInfo.end.r)
			{
				pathInfo.valid = true;	//	valid empty path (zero steps)
				pathInfo.done = true;	//	we're done working
				return true;	//	done
			}

			pathInfo.frontier = [];	//	list of hexes to explore from
			pathInfo.frontier.push({ q: pathInfo.start.q, r: pathInfo.start.r, id: startEntry.id, priority: 0 });

			//	for tracking info about visited locations, (and knowing which I've visited)
			//	I'm using a unique "id" for each hex,
			//	which is set when the hexGrid is initialized above.
			//	The id is used here as a key in a hash of visited locations.
			//	This approach is easy and convenient in javascript, fairly optimal, and hasn't had problems
			//	in the year I've been using it.
			pathInfo.visited = {};
			pathInfo.visited[startEntry.id] = { q: pathInfo.start.q, r: pathInfo.start.r, fromID: -1, cost: 0 };

			//	for partial/failed pathing, keep track of where we got closest (optional)
			if (pathInfo.findClosest)
			{
				pathInfo.closest = {
					dist: 99999999,	//	todo: actual distance from start
					id: startEntry.id,
					pos: { q: pathInfo.start.q, r: pathInfo.start.r },
				};
			} else
				pathInfo.closest = null;
			
			if (pathInfo.blockingSet === void 0)
				pathInfo.blockingSet = 'main';
				

			pathInfo.done = false;	//	actively working on this path
		}

		//	spend several cycles doing the pathfinding work
		var cycles = 0;
		while (
			(pathInfo.cycles === -1 || cycles < pathInfo.cycles)
			&& pathInfo.frontier.length)
		{
			//	take current point in frontier that we're exploring, and expand from there.
			var current = pathInfo.frontier[0];	//	grab next one
			pathInfo.frontier.splice(0, 1);	//	remove it from frontier
			var curVisited = pathInfo.visited[current.id];	//	get visited entry (mostly for cost below) (there's always an entry for anything in the frontier)

			//	get neighbors, avoiding full blocked
			var nList = this.getNeighbors(current.q, current.r, true);

			//	A simple (optional) modification for more natural paths for human thinking...
			//	When searching neighbors to expand our frontier, prioritize entries that are closer
			//	in euclidian (normal XY) distances.
			//	This is potentially much more expensive!
			//	Also, this will need to get redone when we do A*, I think..
			//	Also, if we're serious about this, maybe use a different getNeighbors() function above,
			//	and in fact set up a bunch of them that prioritize various things...
			if (pathInfo.settings.prioritizeXYDistance)
			{
				nList = this.sortListByXYDistance(nList, pathInfo.end.q, pathInfo.end.r);
			}

			//	look at each neighbor
			for (var nIndex = 0; nIndex < nList.length; nIndex++)
			{
				var n = nList[nIndex];
				var entry = this.getEntryAt(n.q, n.r);

				var newCost = curVisited.cost + entry.blocking[pathInfo.blockingSet];

				if (!pathInfo.visited[entry.id] || newCost < pathInfo.visited[entry.id].cost)	//	haven't already visited this tile, or new path is less expensive
				{
					//	add it to our frontier
					var newFrontierEntry = { q: n.q, r: n.r, id: entry.id, priority: newCost };
					//	use newCost as a priority, and keep frontier an ordered list of locations by priority.
					//	todo: make this optional, since it's expensiveish, and some people don't use blocking costs.
					//		and in that case, we just want to append.
					//	todo: I wonder if it would be faster to maintain this list in reverse order, where high priority is end of list?
					var fIndex = 0;
					for (; fIndex < pathInfo.frontier.length; fIndex++)
					{
						if (newCost < pathInfo.frontier[fIndex].priority)	//	insert here
							break;
					}
					pathInfo.frontier.splice(fIndex, 0, newFrontierEntry);
					//	OLD: pathInfo.frontier.push(newFrontierEntry);

					//	remember that we've seen this tile, and here's what we know about it and how we got there.
					//	in the case of having arrived here from a cheaper path, we just replace the old entry entirely.
					pathInfo.visited[entry.id] = { fromQ: current.q, fromR: current.r, fromID: current.id, q: n.q, r: n.r, cost: newCost };

					//	is that where we were heading?  If so, we're done!
					if (n.q === pathInfo.end.q && n.r === pathInfo.end.r)
					{
						//	reconstruct path and return.
						//	todo: besides q and r, include dq and dr deltas?
						var curID = entry.id;
						while (curID !== pathInfo.startID)
						{
							var spot = pathInfo.visited[curID];
							pathInfo.path.unshift({ q: spot.q, r: spot.r });
							curID = pathInfo.visited[curID].fromID;
						}

						//	let go of our data
						pathInfo.frontier = void 0;
						pathInfo.visited = void 0;

						pathInfo.totalCost = newCost;
						pathInfo.valid = true;	//	mark our final path valid
						pathInfo.done = true;	//	we're done
						return true;	//	done
					}

					//	is it at least closer than we've been?
					if (pathInfo.findClosest)
					{
						var dist = rat.HexGrid.distance(n.q, n.r, pathInfo.end.q, pathInfo.end.r);
						if (dist < pathInfo.closest.dist)
						{
							pathInfo.closest.totalCost = newCost;
							pathInfo.closest.dist = dist;
							pathInfo.closest.pos = { q: n.q, r: n.r };
							pathInfo.closest.id = entry.id;
						}
					}

				}
			}
			cycles++;
		}
		//	done with cycles allocated, or with the whole job (no more locations to check).

		if (pathInfo.frontier.length)	//	not done - still working on it.
		{
			return false;
		}
		else		//	done, but didn't find a path!
		{
			//	were we supposed to find closest?
			if (pathInfo.findClosest)
			{
				//	reconstruct path and return.
				//	@todo consolidate with above path reconstruction code, when we move to a new module.
				var curID = pathInfo.closest.id;
				while (curID !== pathInfo.startID)
				{
					var spot = pathInfo.visited[curID];
					pathInfo.path.unshift({ q: spot.q, r: spot.r });
					curID = pathInfo.visited[curID].fromID;
				}
			}

			//	let go of our data
			pathInfo.frontier = void 0;
			pathInfo.visited = void 0;

			pathInfo.valid = false;
			pathInfo.done = true;
			return true;
		}
	};

	//
	//	Flood fill from this hex out, wherever we touch matching blocking cost.
	//	For each matching hex, call the provided callback (standard callback used everywhere in hexgrid)
	//
	//	It's OK for the callback to change the blocking on a hex - we will have already processed that hex.
	//
	//	todo: is this a useful function?  Maybe support as a multi-pass call, like pathfinding?
	//	This function should also probably go in the separate pathfinding module we're planning on making.
	//
	//	Todo: Dang, this should probably return a *list*, not a count.  That'd be way more useful.
	//
	HexGrid.prototype.floodFill = function (startQ, startR, options, callback, userData)
	{
		var pathInfo = {};	//	just in case we do eventually support multi-pass...
		
		pathInfo.blockingSet = 'main';
		if (options && options.blockingSet !== void 0)
			pathInfo.blockingSet = options.blockingSet;
		
		var startEntry = this.getEntryAt(startQ, startR);

		var matchBlocking = startEntry.blocking[pathInfo.blockingSet];	//	initial blocking value at this location
		
		pathInfo.startID = startEntry.id;
		pathInfo.frontier = [];	//	list of hexes to explore from
		pathInfo.frontier.push({ q: startQ, r: startR, id: startEntry.id });
		pathInfo.visited = {};
		pathInfo.visited[startEntry.id] = { q: startQ, r: startR };
		pathInfo.visitedCount = 1;
		
		//	standard args passed to callback
		var args = {
			hexGrid : this,
			//	entry
			//	q, r
			//	value (will be entry.value)
			userData : userData,
		};

		//	fill
		while (pathInfo.frontier.length)
		{
			//	take current point in frontier that we're exploring, and expand from there.
			var current = pathInfo.frontier[0];	//	grab next one
			pathInfo.frontier.splice(0, 1);	//	remove it from frontier

			//	process it
			var entry = this.getEntryAt(current.q, current.r);
			//callback(this, entry, current.q, current.r, entry.value, userData);
			args.entry = entry;
			args.q = current.q;
			args.r = current.r;
			args.value = args.entry.value;
			callback(args);

			//	get neighbors
			var nList = this.getNeighbors(current.q, current.r);

			//	look at each neighbor
			for (var nIndex = 0; nIndex < nList.length; nIndex++)
			{
				var n = nList[nIndex];
				var entry = this.getEntryAt(n.q, n.r);

				if (!pathInfo.visited[entry.id] && entry.blocking[pathInfo.blockingSet] === matchBlocking)
				{
					//	add it to our frontier
					var newFrontierEntry = { q: n.q, r: n.r, id: entry.id };
					pathInfo.frontier.push(newFrontierEntry);

					//	remember that we've seen this tile and added it to our frontier
					pathInfo.visited[entry.id] = { q: n.q, r: n.r };

					pathInfo.visitedCount++;
				}
			}
		}
		//	if needed, we could wait until we collect the full list and then call the callback...
		//	but I like the idea of the callback being able to abort the fill operation... (a potential feature currently unimplemented)

		return pathInfo.visitedCount;
	};
	
	//	Load some data from this standard layered data object,
	//	generally created by using the Tiled editor.
	//	For each cell that we get data for, all the layer data is collected, and the callback provided is called with that data.
	//	(standard hexgrid callback with additional argument "layerData[]"
	//	todo:
	//		options:
	//			support completely replacing our defined data structure with this new data's structure/dimensions
	//			and if not, then warn if data dimensions are bigger than our grid here.
	//		support other stagger values in data
	HexGrid.prototype.loadLayersFromData = function (data, callback, opts)
	{
		var layers = data['layers'];
		if (!layers)
			return;
		
		//	assuming "staggeraxis":"y",
		//	assuming "staggerindex":"odd",
		
		//	If there are several layers, we're ultimately going to set several layers' of data at once,
		//	in each cell, so let's just walk through all the cells we have and find their data all at once,
		//	 if it's there.
		this.forEachHex(function (args)
		{
			//args.entry.value
			var layerData = [];
			var gotData = false;
		
			//	assuming "staggerindex":"odd" here to convert from qr to xy
			var y = args.r;
			var x = args.q + rat.math.floor(args.r/2);
			
			//	for each layer, see if there's data that fits us, and collect it.
			for (var layerIndex = 0; layerIndex < layers.length; layerIndex++)
			{
				var layer = layers[layerIndex];
				var data = layer['data'];
				var width = layer['width'];
				var height = layer['height'];
				var dataIndex = y * width + x;

				if (x < width && y < height && layer['type'] === 'tilelayer' && data && data[dataIndex] !== void 0)
				{
					layerData[layerIndex] = data[dataIndex];
					gotData = true;
				}
			}
			//	and do something with that data.
			if (gotData)
			{
				if (callback)
				{
					args.layerData = layerData;
					callback(args);
				} else {	//	no callback - what do they want from us?
					//	set layer data in value directly.  Let's hope they can deal with that.
					if (!args.entry.value || typeof(args.entry.value) !== 'object')
						args.entry.value = {};
					args.entry.value = {layerData:layerData};
				}
			}
		});
		
	};
	

	//	create a single-hex html5 context path for later stroking or filling or whatever.
	HexGrid.prototype.makeHexContextPath = function (ctx, q, r, inset)
	{
		inset = inset || 0;

		var pos = this.axialToPixelPos(q, r);
		var x = pos.x;
		var y = pos.y;

		var h = this.hexHeight;
		var w = this.hexWidth;

		//	todo: probably these inset/2 values should be something else, like sqrt(3) or 2/3 or something. :)
		ctx.beginPath();
		ctx.moveTo(x + inset - w / 2, y + inset / 2 - h / 4);	//	top left
		ctx.lineTo(x, y + inset - h / 2);		//	top
		ctx.lineTo(x - inset + w / 2, y + inset / 2 - h / 4);
		ctx.lineTo(x - inset + w / 2, y - inset / 2 + h / 4);
		ctx.lineTo(x, y - inset + h / 2);		//	bottom
		ctx.lineTo(x + inset - w / 2, y - inset / 2 + h / 4);
		//ctx.lineTo(xxx, yyy);
		ctx.closePath();
	};

	//	draw (stroke) one hex (using current ctx lineWidth and strokeStyle)
	//	given q,r coordinates, we calculate where in hexgrid space this value is.
	//	mostly debug?
	HexGrid.prototype.strokeOneHex = function (ctx, q, r, inset)
	{
		this.makeHexContextPath(ctx, q, r, inset);
		ctx.stroke();
	};

	//	draw (fill) one hex (using current ctx fill info)
	//	given q,r coordinates, we calculate where in hexgrid space this value is.
	//	mostly debug?
	HexGrid.prototype.fillOneHex = function (ctx, q, r, inset)
	{
		this.makeHexContextPath(ctx, q, r, inset);
		ctx.fill();
	};

	//	draw (stroke or fill depending on each hex's debugStyle) the full grid.
	//	useful for debugging, really.
	//	set lineWidth and strokeStyle yourself before calling.
	HexGrid.prototype.drawGrid = function (ctx)
	{
		//	todo: for optimal performance, draw as many continuous lines as we can, instead of each hex,
		//	e.g. draw the zigzaggy tops for a bunch at once, and then connecting vertical lines.

		//	draw whatever our known valid data is.
		//	that way we don't care how we were set up, just what's valid data, and we draw that.
		for (var y = 0; y < this.data.length; y++)
		{
			var offset = this.data[y].offset;
			for (var x = 0; x < this.data[y].length; x++)
			{
				var q = x + offset;
				var r = y;
				this.strokeOneHex(ctx, q, r);

				var entry = this.getEntryAt(q, r);
				if (entry.debugStyle)
				{
					ctx.fillStyle = entry.debugStyle;
					this.fillOneHex(ctx, q, r);
				}
			}
		}

	};

	//	more globally accessible class
	rat.HexGrid = HexGrid;

});

//--------------------------------------------------------------------------------------------------
//
//	Rat 2D Collision math library (detect intersection, containment, etc.)
//	Requires rat.Shapes
//

//------------ rat.collision.2D ----------------
rat.modules.add( "rat.utils.r_collision2d",
[
	"rat.graphics.r_graphics",
	"rat.debug.r_console",
	"rat.math.r_math",
	"rat.utils.r_shapes",
], 
function(rat)
{
	/** Collision module */
	rat.collision2D = {};
	
	/**
	 * Test if a point is in a rectangle
	 * @param {Object} v The vector {x, y}
	 * @param {Object} r The rect {x, y, w, h}
	 * @return {boolean} True if the point is in the rect
	 */
	rat.collision2D.pointInRect = function(/*rat.Vector*/ v, /*rat.shapes.Rect*/ r)
	{
		return !(v.x < r.x || v.y < r.y || v.x > r.x + r.w || v.y > r.y + r.h);
	};
	
	/**
	 * modify this point to keep it in a rectangle space.
	 * Like rat.Vector.prototype.limitToRect, but without a real point or rect object needed
	 */
	rat.collision2D.limitPointToSpace = function (point, x, y, w, h)
	{
		if (point.x < x)
			point.x = x;
		if (point.y < y)
			point.y = y;
		if (point.x > x + w)
			point.x = x + w;
		if (point.y > y + h)
			point.y = y + h;
	};

	/**
	 * Test if two rects collide
	 * @param {Object} r1 a rat rectable object {x,y,w,h}
	 * @param {Object} r2 a rat rectable object {x,y,w,h}
	 * @return {boolean} True if the rects collide
	 */
	rat.collision2D.rectOverlapsRect = function(/*rat.shapes.Rect*/ r1, /*rat.shapes.Rect*/ r2)
	{
		//	It's easier to think of this as "when do two rects not overlap?"  answer:  when one rect is entirely above, left, below, right.
		return !(r1.y + r1.h < r2.y	||	//	r1 is above
				 r1.y > r2.y + r2.h	||	//	r1 is below
				 r1.x + r1.w < r2.x	||	//	r1 is left
				 r1.x > r2.x + r2.w	);	//	r1 is right
	};

	
	/**
	 * Test if two circles collide.  This support offsetting the circles
	 */
	rat.collision2D.circlesCollide_Offset = function (c1, at1, c2, at2)
	{
		if (!c1 || !c2)
			return void 0; // False-ish

		//	Combined radius
		var radii = c1.radius + c2.radius;

		//	distance between the two centers
		var deltaX = (c2.center.x + at2.x) - (c1.center.x + at1.x);
		var deltaY = (c2.center.y + at2.y) - (c1.center.y + at1.y);
		var dist = rat.math.sqrt((deltaX * deltaX) + (deltaY * deltaY));

		//	The collide if the distance is <= the combine radii
		if (dist > radii)
			return void 0; // again, false-ish
		else
			return { dist: dist, radii: radii };
	};

	/**
	 * Test if two circles collide
	 */
	rat.collision2D.circlesCollide = function( c1, c2 )
	{
		if( !c1 || !c2 )
			return void 0; // False-ish
		
		//	Combined radius
		var radii = c1.radius + c2.radius;

		//	distance between the two centers
		var deltaX = (c2.center.x - c1.center.x);
		var deltaY = (c2.center.y - c1.center.y);
		var dist = rat.math.sqrt( (deltaX * deltaX) + (deltaY * deltaY) );

		//	The collide if the distance is <= the combine radii
		if( dist > radii )
			return void 0; // again, false-ish
		else
			return {dist:dist, radii:radii};
	};

	/**
	 * Test if two rects collide.  Supports offset.
	 */
	rat.collision2D.rectsCollide_Offset = function (r1, at1, r2, at2)
	{
		//	It's easier to think of this as "when do two rects not overlap?"
		//	answer:  when one rect is entirely above, left, below, right.
		var r1x = r1.x + at1.x;
		var r1y = r1.y + at1.y;
		var r2x = r2.x + at2.x;
		var r2y = r2.y + at2.y;
		return !(r1y + r1.h < r2y ||	//	r1 is above
				 r1y > r2y + r2.h ||	//	r1 is below
				 r1x + r1.w < r2x ||	//	r1 is left
				 r1x > r2x + r2.w);	//	r1 is right
	};

	/**
	 * Test if two rects collide
	 */
	rat.collision2D.rectsCollide = rat.collision2D.rectOverlapsRect;

	/**
	 * do a circle and a rect collide.  Supports offset
	 */
	rat.collision2D.circlesAndRectCollide_Offset = function (c, cAt, r, rAt)
	{
		/* Based on code in the native engine*/
		var s, d = 0;
		var rr;
		if (c.squaredRadius)
			rr = c.squaredRadius;
		else
			rr = c.radius * c.radius;
		var spherePos = { x: c.center.y + cAt.x, y: c.center.y + cAt.y };
		var boxMin = { x: r.x + rAt.x, y: r.y + rAt.y }; // We only really care about the min here
		var boxMax = { x: boxMin.x + r.w, y: boxMin.y + r.h  };
		// In X?
		if (spherePos.x < boxMin.x)
		{
			s = spherePos.x - boxMin.x;
			d += s * s;
		}
		else if (spherePos.x > boxMax.x)
		{
			s = spherePos.x - boxMax.x;
			d += s * s;
		}

		// In Y?
		if (spherePos.y < boxMin.y)
		{
			s = spherePos.y - boxMin.y;
			d += s * s;
		}
		else if (spherePos.y > boxMax.y)
		{
			s = spherePos.y - boxMax.y;
			d += s * s;
		}

		if (d <= rr)
			return {};
		else
			return void 0;
	};

	/**
	 * do a circle and a rect collide
	 */
	rat.collision2D.circlesAndRectCollide = function (c, r)
	{
		/* Based on code in the native engine*/
		var s, d = 0;
		var rr;
		if (c.squaredRadius)
			rr = c.squaredRadius;
		else
			rr = c.radius * c.radius;
		var spherePos = c.center;
		var boxMin = r; // We only really care about the min here
		var boxMax = { x: r.x + r.w, y: r.y + r.h };
		// In X?
		if (spherePos.x < boxMin.x)
		{
			s = spherePos.x - boxMin.x;
			d += s * s;
		}
		else if (spherePos.x > boxMax.x)
		{
			s = spherePos.x - boxMax.x;
			d += s * s;
		}

		// In Y?
		if (spherePos.y < boxMin.y)
		{
			s = spherePos.y - boxMin.y;
			d += s * s;
		}
		else if (spherePos.y > boxMax.y)
		{
			s = spherePos.y - boxMax.y;
			d += s * s;
		}

		if (d <= rr)
			return {};
		else
			return void 0;
	};

	/**
	 * Test if two shapes collide
	 */
	rat.collision2D.shapesCollide = function (shape1, at1, shape2, at2)
	{
		switch (shape1.type)
		{
			case 'circle':
				switch (shape2.type)
				{
					case 'circle':
						return rat.collision2D.circlesCollide_Offset(shape1, at1, shape2, at2);
					case 'rect':
						return rat.collision2D.circlesAndRectCollide_Offset(shape1, at1, shape2, at2);
					default:
						return void 0;
				}
				break;
			case 'rect':
				switch (shape2.type)
				{
					case 'circle':
						return rat.collision2D.circlesAndRectCollide_Offset(shape2, at2, shape1, at1);
					case 'rect':
						return rat.collision2D.rectsCollide_Offset(shape1, at1, shape2, at2);
					default:
						return void 0;
				}
				break;
			default:
				return void 0;
		}
	};

	/**
	 * Test if a shape collides with a list of shapes
	 */
	rat.collision2D.shapeAndShapeListCollide = function (shape, shapeAt, list, listAt)
	{
		var res;
		var child;
		var iHaveChildren = shape.children && shape.children.length;
		var childHasChildren;
		for (var childIndex = 0, len = list.length; childIndex !== len; ++childIndex)
		{
			//	Do i collide with this child
			child = list[childIndex];
			res = rat.collision2D.shapesCollide(shape, shapeAt, child, listAt);

			//	Did i collide?
			if( res )
			{
				childHasChildren = child.children && child.children.length;
				//	If i have no children, AND child has no children
				//	The this is a collision
				if ( !iHaveChildren && !childHasChildren )
					return res;

				//	If I have children, and he does not, then He needs to be compared vs my children
				if (iHaveChildren && !childHasChildren)
				{
					res = rat.collision2D.shapeAndShapeListCollide(child, listAt, shape.children, shapeAt);
					//	He hit my leaf node
					//	collision because he is also a leaf
					if (res)
						return res;
				}
				else
				{
					//	Test me vs. the childs children
					//	This tells me that I collide with a leaf node.
					res = rat.collision2D.shapeAndShapeListCollide(shape, shapeAt, child.children, listAt);

					// Did I hit a leaf node?
					if (res)
					{
						//	IF I do collide with a leaf node, and I have no children, then I collide
						if (!iHaveChildren)
							return res;

						//	Check my children vs the childs children
						res = rat.collision2D.shapeListsCollide(shape.children, shapeAt, child.children, listAt);
						//	Did one of my leaf nodes collide with one of their leaf nodes
						if (res)
							return res;
					}
				}
			}
		}

		//	Must not have collided
		return void 0;
	};

	/**
	 * Test if two lists of shape objects collide
	 */
	rat.collision2D.shapeListsCollide = function (list1, at1, list2, at2)
	{
		var res;
		for (var childIndex = 0, len = list1.length; childIndex !== len; ++childIndex)
		{
			// A^x -> B^n
			res = rat.collision2D.shapeAndShapeListCollide(list1[childIndex], at1, list2, at2);

			//	They collided!
			if (res)
				return res;
		}

		//	Must not have collided
		return void 0;
	};

	/**
	 * Test if two bounding shapes collide
	 */
	rat.collision2D.BoundingShapesCollide = function(shape1, at1, shape2, at2)
	{
		//	Only continue if their two circles collide
		var collided = rat.collision2D.circlesCollide_Offset(shape1.mBounding, at1, shape2.mBounding, at2 );
		if( !collided )
			return collided;

		//	Test shapes for collision
		//	Shape list vs shape list
		// A^n -> B^n
		return rat.collision2D.shapeListsCollide(shape1.mShapes, at1, shape2.mShapes, at2);
	};
	
	// This portion of the collision2D system requires rat.shapes
	/**
	 * Hidden function used to create a shape from a data structure. used by BoundingShape
	 */
	function CreateShape(data, limits)
	{
		//	Create the correct object
		var newShape;
		switch(data.type)
		{
			case 'circle': // Fall through
			case 'Circle':
			{
				newShape = new rat.shapes.Circle(data);
				newShape.type = 'circle';
				var c  = newShape.center;
				var r = newShape.radius;
				if (limits.left === void 0 || limits.left > c.x - r)
					limits.left = c.x - r;
				if (limits.right === void 0 || limits.right < c.x + r)
					limits.right = c.x + r;
				if (limits.top === void 0 || limits.top > c.y - r)
					limits.top = c.y - r;
				if (limits.bottom === void 0 || limits.bottom < c.y + r)
					limits.bottom = c.y + r;
				break;
			}

			case 'rect': // Fall through
			case 'Rect': // Fall through
			case 'box': // Fall through
			case 'box':
			{
				newShape = new rat.shapes.Rect(data);
				newShape.type = 'rect';
				if (limits.left === void 0 || limits.left > newShape.x)
					limits.left = newShape.x;
				if (limits.right === void 0 || limits.right < newShape.x + newShape.w)
					limits.right = newShape.x + newShape.w;
				if (limits.top === void 0 || limits.top > newShape.y)
					limits.top = newShape.y;
				if (limits.bottom === void 0 || limits.bottom < newShape.y + newShape.h)
					limits.bottom = newShape.y + newShape.h;
				break;
			}

			default:
				rat.console.log("ERROR: Un-supported shape found in bounding shape data: " + data.type);
				return void 0;
		}

		//	Make sure that the shape has its type.
		newShape.type = data.type;

		if (data.name)
			newShape.name = data.name;

		//	Create this shape's children
		newShape.children = [];
		if(data.children)
		{
			for(var index = 0, len = data.children.length; index !== len; ++index)
			{
				newShape.children.push(CreateShape(data.children[index], limits));
			}
		}

		//	return the created shape (and its children)
		return newShape;
	}

	/**
		* Type data structure layout defines a bounding shape (with the options of shape groups)
		* @constructor
		* @param {Array} data
		* The layout of data is [
		* <{
		*		type: 'circle',
		*		x:,
		*		y:,
		*		radius:,
		*		<inDegrees:>,
		*		children:[ <subshapes of either circle or rect> ]
		* }>
		* <{
		*		type: 'rect',
		*		x:,
		*		y:,
		*		w:,
		*		h:,
		*		children:[ <subshapes of either circle or rect> ]
		* }>
		* ]
		*/
	rat.collision2D.BoundingShape = function (data)
	{
		//	Process default to array
		if(Array.isArray(data) === false)
			data = [data];

		this.mLimits = {
			left: void 0,
			right: void 0,
			top: void 0,
			bottom: void 0
		};

		//	Create my shapes
		//	Get the extends of the shapes here.

		this.mShapes = [];
		for(var index = 0, len = data.length; index !== len; ++index)
			this.mShapes.push(CreateShape(data[index], this.mLimits));

		//	Sanity on the limits
		this.mLimits.left = this.mLimits.left || 0;
		this.mLimits.right = this.mLimits.right || 0;
		this.mLimits.top = this.mLimits.top || 0;
		this.mLimits.bottom = this.mLimits.bottom || 0;

		//	What is a wrapper size
		this.mSize = {
			x: this.mLimits.right - this.mLimits.left,
			y: this.mLimits.bottom - this.mLimits.top,
		};

		//	extract calculations
		var halfSize = {
			x: this.mSize.x/2,
			y: this.mSize.y/2
		};

		//	What is our bounding circle
		this.mBounding = new rat.shapes.Circle({
			x: this.mLimits.left + halfSize.x,
			y: this.mLimits.top + halfSize.y,
			squaredRadius: (halfSize.x * halfSize.x) + (halfSize.y * halfSize.y),
		});
	};

	/**
	 * Draw this bounding shape.
	 * @param {Object} at
	 * @param {Object} boundingColor
	 * @param {Object} shapesColor
	 */
	rat.collision2D.BoundingShape.prototype.debugDraw = function (at, boundingColor, shapesColor)
	{
		rat.graphics.save();
		var ctx = rat.graphics.ctx;
		var colorString = boundingColor.toString();
		ctx.strokeStyle = colorString;
		ctx.lineWidth = 1;
		rat.graphics.translate(at.x, at.y);

		//	Draw the bounding rectangle.
		var limits = this.mLimits;
		var size = this.mSize;
		rat.graphics.ctx.strokeRect(limits.left, limits.top, size.x, size.y);

		//	Draw the bounding circle
		rat.graphics.drawCircle(this.mBounding);

		colorString = shapesColor.toString();
		ctx.strokeStyle = colorString;

		//	Draw the shape list
		rat.graphics.drawShapeList(this.mShapes);

		rat.graphics.restore();
	};
	
			
	//
	//	Find the distance from this point to this line.
	//	If the nearest point is within the segment given, give the point where it is closest, and return true.
	//	If the nearest point is NOT on the segment given, still give the point where it's closest, but return false.
	//	(either way, return distance)
	//
	//	This code is adapted from another adaptation of an approach by Damian Coventry:
	//	http://astronomy.swin.edu.au/~pbourke/geometry/pointline/
	//	http://astronomy.swin.edu.au/~pbourke/geometry/pointline/source.c
	//
	//	NOTE: UNTESTED CODE!  I ported this and then decided not to use it.
	rat.collision2D.pointToLine = function(point, lineA, lineB) {
		var res = {onLine : false};
		
		var dx = lineA.x - lineB.x;
		var dy = lineA.y - lineB.y;
		var lenSq = dx*dx+dy*dy;

		var u = ( ( ( point.x - lineA.x ) * ( lineB.x - lineA.x ) ) +
			( ( point.y - lineA.y ) * ( lineB.y - lineA.y ) ) ) /
			( lenSq );

		var intersection = {};
		intersection.x = lineA.x + u * ( lineB.x - lineA.x );
		intersection.y = lineA.y + u * ( lineB.y - lineA.y );
		res.closePoint = intersection;

		var idx = point.x - intersection.x;
		var idy = point.y - intersection.y;
		res.distanceSquared = idx*idx+idy*idy;
		
		res.onLine = (u >= 0.0 && u <= 1.0);
		
		return res;
	};
	
} );
//--------------------------------------------------------------------------------------------------
/*

	rectangle list manager.

	Collect and manage a list of 2D rectangles.
	Handle adding, removing, coalescing overlapping rects, etc.
	
	useful for dirty rectangle lists, among other things.
*/
rat.modules.add("rat.graphics.r_rectlist",
[],
function (rat)
{
	/** 
	    RectList object
	    @constructor
	   
 */
	rat.RectList = function ()
	{
		this.list = [];
	};
	//rat.RectList.prototype.blah = true;	//	whatever

	//	clear rect list
	rat.RectList.prototype.clear = function ()
	{
		this.list = [];
	};

	//	snap this rectangle to an even pixel alignment, one pixel around.
	//	This is important for some uses, when rectangles have fractional xywh values,
	//	particularly when we're dealing with antialiasing.
	//	The usage of this is optional, and handled automatically if turned on - see "snapEven" flag.
	//	Odd note:  When graphics are being scaled up (see rat.graphics.globalScale), this still has problems when we clip... :(
	//		How to fix that?  We ideally need these things to align to the final pixel, not to some value that later gets scaled anyway...
	//		One way to fix this is to use bigger target resolutions, instead of targetting small space and scaling up (target big space and scale down).
	//		Can we hack this for now by using bigger numbers?  Nope, it doesn't solve the problem with the final clip being misaligned when graphics applies its scale...
	rat.RectList.prototype.snap = function (addR)
	{
		var r = {};

		r.x = (addR.x - 1 | 0);	//	hmm... why is this -1 necessary?  It seems to be.  Maybe something about pixel scale?
		r.y = (addR.y - 1 | 0);
		r.w = ((addR.w + 1 + (addR.x - r.x) + 0.999999999) | 0);
		r.h = ((addR.h + 1 + (addR.y - r.y) + 0.999999999) | 0);

		return r;
	};

	//	add rect
	//	todo: maintain in some kind of binary searchable order
	rat.RectList.prototype.add = function (addR)
	{
		var r;
		//	if we're supposed to, then snap to outside even boundaries,
		//	to avoid problems with precision errors resulting in bad drawing.
		if (this.snapEven)
			r = this.snap(addR);
		else
			r = { x: addR.x, y: addR.y, w: addR.w, h: addR.h };	//	copy, so we don't get changed when original changes

		//	do some optimizations, based on this rectangle being similar to others already in the list.
		//	TODO: make this optional, based on flag during setup.
		for (var i = this.list.length - 1; i >= 0; i--)
		{
			var t = this.list[i];
			//	short names for right/bottom edges
			var rright = r.x + r.w;
			var rbottom = r.y + r.h;
			var tright = t.x + t.w;
			var tbottom = t.y + t.h;

			//	see if new rectangle is fully included already in another rectangle.
			//	if so, bail now! (don't add r at all)
			if (r.x >= t.x && r.y >= t.y && rright <= tright && rbottom <= tbottom)
			{
				//console.log("add rect inside existing");
				return;
			}
			//	If new rectangle fully includes an existing rectangle, remove *that* rectangle.
			//	keep looping, in case we end up including more than one!
			//	This means a new rectangle could eat up several existing ones, which is good.
			//	At the end of this loop, the new one will be added (or otherwise resolved).
			if (r.x <= t.x && r.y <= t.y && rright >= tright && rbottom >= tbottom)
			{
				//console.log("add rect outside existing");
				this.list.splice(i, 1);
				continue;
			}

			//	OK, the above checks are good and basically no-brainers.  Certainly effective.
			//	Here's where it's a little more heuristic.
			//	How much are these rects overlapping?  If a lot, merge them into one!
			//	Note that this is a very common case, because a moving object will almost always
			//		have a new bounds slightly shifted from previous bounds.
			//	We might need to make this optional, and configurable in how aggressive it is.
			//	TODO: deal with a need for multiple passes.  Merging rects could mean another existing rect is suddenly partly overlapped/consumed.
			//	TODO: optimize all this logic, especially combined with the above?  Maybe not a performance concern.
			//		e.g. quick check to see if there's any overlap at all, and if not, move on,
			//			and if so, then find out what kind, or entirely containing/contained, etc.
			//	TODO: yikes, lots of individual checks below.  Can somehow be simplified?
			var horizOverlap = 0;
			var vertOverlap = 0;
			var left, right, top, bottom;

			//	horizontal checks
			//	make sure there's *some* overlap
			if (!(rright < t.x || tright < r.x) && !(rbottom < t.y || tbottom < r.y))
			{
				if (r.x < t.x)	//	left edge of r is farther left
				{
					left = r.x;
					if (rright > tright)	//	r includes t entirely
					{
						horizOverlap = t.w;
						right = rright;
					} else
					{	//	r overlaps on left
						horizOverlap = rright - t.x;
						right = tright;
					}
				} else
				{
					left = t.x;
					if (tright > rright)	//	t includes r entirely
					{
						horizOverlap = r.w;
						right = tright;
					} else
					{	//	r overlaps on right
						horizOverlap = tright - r.x;
						right = rright;
					}
				}

				//	now vertical cases
				if (r.y < t.y)	//	top edge of r is farther up
				{
					top = r.y;
					if (rbottom > tbottom)	//	r includes t entirely
					{
						vertOverlap = t.h;
						bottom = rbottom;
					} else
					{	//	r overlaps on top
						vertOverlap = rbottom - t.y;
						bottom = tbottom;
					}
				} else
				{
					top = t.y;
					if (tbottom > rbottom)	//	t includes r entirely
					{
						vertOverlap = r.h;
						bottom = tbottom;
					} else
					{	//	r overlaps on bottom
						vertOverlap = tbottom - r.y;
						bottom = rbottom;
					}
				}

				//	now, is that overlap worth it?  At this point we assume horizOverlap and vertOverlap are defined.
				//	For now, require our overlap to be X% of r, but could also check t.
				//	The idea here is that we don't want to always merge.  If 2 rects are barely touching, merging them might resulting
				//	in a lot of things being dirtied that don't really need it.  So, just merge if they're pretty close...
				if (horizOverlap * vertOverlap > r.w * r.h * 0.7)
				{
					//	Huh?
					if (t.x + t.w > right)
					{
						console.log("LOSS");
					}

					//	merge into new r
					r.x = left;
					r.y = top;
					r.w = right - left;
					r.h = bottom - top;
					//	like above, let's kill t in the list and continue looping.
					this.list.splice(i, 1);
				}

			}	//	end of total overlap check


		}	//	end of loop through existing rects

		//	we made it through with r intact - go ahead and add it.
		this.list.push(r);
	};
	//	remove rect, judging from position/size
	//rat.RectList.prototype.remove = function (r)
	//{
	//	//	NOT IMPLEMENTED.
	//};

	//	return true if this rect intersects at all with any rect in my list.
	rat.RectList.prototype.hits = function (r)
	{
		if (this.snapEven)
			r = this.snap(r);

		for (var i = 0; i < this.list.length; i++)
		{
			var t = this.list[i];
			if (r.x + r.w >= t.x
					&& r.x <= t.x + t.w
					&& r.y + r.h >= t.y
					&& r.y <= t.y + t.h)
				return true;
		}

		return false;
	};

	//	Useful utilities - not always needed, but when this rectlist is used as a dirty list, these are nice.

	//	erase all our rects from this ctx
	rat.RectList.prototype.eraseList = function (ctx)
	{
		for (var i = 0; i < this.list.length; i++)
		{
			var t = this.list[i];
			ctx.clearRect(t.x, t.y, t.w, t.h);
		}
	};

	//	set ctx clip region to the total dirty space.
	//	!!
	//		This does a context save, so you MUST CALL unclip() below when you're done with this clipping operation.
	//	!!
	//	useList here is optional, and generally not useful outside debugging.  Usually, you want to use this rectList's list.
	rat.RectList.prototype.clipToList = function (ctx, useList)
	{
		ctx.save();
		this.listToPath(ctx, useList);
		ctx.clip();

	};
	rat.RectList.prototype.unclip = function (ctx)
	{
		ctx.restore();
	};

	//	make a path in the given ctx using our rect list
	//	(or another list, if one was given to us - mostly useful for debugging)
	rat.RectList.prototype.listToPath = function (ctx, list)
	{
		if (!list)
			list = this.list;
		ctx.beginPath();
		for (var i = 0; i < list.length; i++)
		{
			var t = list[i];
			//ctx.rect(t.x, t.y, t.w, t.h);
			ctx.moveTo(t.x, t.y);
			ctx.lineTo(t.x + t.w, t.y);
			ctx.lineTo(t.x + t.w, t.y + t.h);
			ctx.lineTo(t.x, t.y + t.h);
			ctx.lineTo(t.x, t.y);
		}
	};

});

//--------------------------------------------------------------------------------------------------
//
//	Test video object.
//
rat.modules.add( "rat.test.r_test_video",
[
	{ name: "rat.test.r_test", processBefore: true },
	
	"rat.graphics.r_graphics",
	"rat.graphics.r_video",
	"rat.ui.r_screen",
	"rat.ui.r_screenmanager",
	"rat.ui.r_ui_textbox",
],
function(rat)
{
	var video;
	var time = 0;
	var logBox;
	var logLines = [];
	var updateAdded = false;
	function log(txt)
	{
		if (!logBox)
			return;
		var fullText = "Test: Video\n---------";
		logLines.push(txt);
		if (logLines.length > 25)
			logLines.shift();
		for (var i = 0; i < logLines.length; ++i)
		{
			fullText += "\n";
			fullText += logLines[i];
		}
		logBox.setTextValue(fullText);
	};

	rat.test.video = {
		gotEvent: function( from, type, varargs )
		{
			log( "Got on" + (type || varargs) + " event.." );
		},
		setup: function ()
		{
			if (video)
				video.destroy();
			if( !updateAdded)
			{
				rat.test.tests.push({ update: rat.test.video.update });
				updateAdded = true;
			}
			video = new rat.graphics.Video({
				foreground: false,
				volume: 1,
				onload: rat.test.video.gotEvent,
				onend: rat.test.video.gotEvent,
				ondestroy: rat.test.video.gotEvent,
				onstartbuffering: rat.test.video.gotEvent,
				onendbuffering: rat.test.video.gotEvent,
			});
			video.play("res/video/small");

			var screenWidth = rat.graphics.SCREEN_WIDTH;
			var screenHeight = rat.graphics.SCREEN_HEIGHT;

			//	screens are just UI elements.  Make a container to hold all UI.
			var container = new rat.ui.Screen();
			container.setPos(0, 0);
			container.setSize(screenWidth, screenHeight);

			rat.screenManager.setUIRoot(container);

			logBox = new rat.ui.TextBox("Test: Video");
			logBox.setFont("arial bold");
			logBox.setFontSize(20);
			logBox.setPos(32, 32);
			logBox.setSize(screenWidth, 30);
			logBox.setAlign(rat.ui.TextBox.alignLeft);
			logBox.setBaseline(rat.ui.TextBox.baseLineTop);
			logBox.setColor(new rat.graphics.Color(180, 180, 210));
			container.appendSubElement(logBox);

			log("Test Setup");
		},

		update: function ( deltaTime )
		{
			if (video)
				video.update(deltaTime);

			function justPassed(testTime)
			{ return (was < testTime && time >= testTime); }

			var was = time;
			time += deltaTime;
			if (justPassed(1))
			{
				log("Destroyed");
				video.destroy();
				video = void 0;
			}
			else if (justPassed(2))
			{
				log("Created low vol");
				video = new rat.graphics.Video({
					file: "res/video/small",
					foreground: false,
					volume: 0.1,
					onload: rat.test.video.gotEvent,
					onend: rat.test.video.gotEvent,
					ondestroy: rat.test.video.gotEvent,
					onstartbuffering: rat.test.video.gotEvent,
					onendbuffering: rat.test.video.gotEvent,
				});
				video.play();
			}
			else if (justPassed(3))
			{
				log("Paused");
				video.pause();
			}
			else if (justPassed(4))
			{
				log("Resumed");
				video.resume();
			}
			else if (justPassed(5))
			{
				log("Stopped");
				video.stop();
			}
			else if (justPassed(6))
			{
				log("Played");
				video.play();
			}
		}
	};
	
});
//--------------------------------------------------------------------------------------------------
//
//	Rat event map module
//
//	Handle registering and firing events for the designers to respond to.
//
//	Based somewhat on Wraith, but not as featureful
//
rat.modules.add( "rat.utils.r_eventmap",
[
	"rat.debug.r_console",
], 
function(rat)
{
	rat.eventMap = {};
	
	var events = {};
	
	//	Register new event handler.
	rat.eventMap.register = function( eventName, func )
	{
		//	No point to register if no func.
		if( !func )
			return;
		eventName = eventName.toUpperCase();
		
		//	First handler?
		if( !events[eventName] )
			events[eventName] = [func];
		else
			events[eventName].push( func );
	};
	rat.eventMap.registerHandler = rat.eventMap.register;
	rat.eventMap.add = rat.eventMap.register;
	
	//	Fire an event 
	rat.eventMap.fireEvent = function( eventName, forObj )
	{
		eventName = eventName.toUpperCase();
		var eventList = events[eventName];
		if( !eventList )
			return;
		var args = Array.prototype.slice.call(arguments);
		args.splice(0, 2);
		//	Should i add the eventName?
		var endAt = eventList.length;
		for( var index = 0; index < endAt; ++index )
			eventList[index].apply( forObj, args );
	};
	rat.eventMap.fire = rat.eventMap.fireEvent;
	
} );
//--------------------------------------------------------------------------------------------------
//
//	Common kongregate system-level support, including initializing the API
//
//	Note that we aggressively fake kong API access when we're not actually running inside Kongregate,
//	for ease of development.  See r_user_kong
//

rat.modules.add( "rat.os.r_kong",
[
	{ name: "rat.os.r_system", processBefore: true},
	{ name: "rat.os.r_user_kong", processBefore: true},
	{ name: "rat.utils.r_timer", processBefore: true},
	"rat.debug.r_console",
],
function(rat)
{
	rat.system.kong = rat.system.kong || {};
	var rkong = rat.system.kong;
	
	rkong.init = function()
	{
		rkong.fake = true;	//	let's assume we're faking it until told otherwise
		rkong.status = 'init';
		
		//	kong init
		if (typeof(kongregateAPI) === "undefined")
		{
			rat.console.log("Kong: kong module included, but no kongregateAPI available...");
			rat.console.log("remember to add kongregate API to html file");
			
		} else {
			//	some debugging...
			
			//	IN KONGREGATE SITE,
			//	window top is false, 
			//	window parent is false.
			//	parent is non-null.
			//	(window === window.top) returns false if we're in an iframe, which is what kongregate uses.
			//	So, it's a good way for now to detect kongregate.
			//	TODO: check our URL and see if we are konggames or something?
			//	We need to do this because kongregateAPI.loadAPI() simply never calls our callback if it's not really on kongregate,
			//	and we need to be able to fake the API otherwise.
			//	TODO:  Move all this to an r_kong module that encapsulates this stuff.
			//	better yet... move it to a more abstract r_host API or r_hostService or something.
			
			//rat.console.log("window top? " + (window === window.top));
			//rat.console.log("window parent? " + (window === parent));
			//rat.console.log("parent? " + (!!parent));
			
			if (window !== window.top)
			{
				rat.console.log("Kong: Initializing API...");
				rkong.status = 'pending';
				
				//	this load will take a moment to complete, and call our callback...
				kongregateAPI.loadAPI(function() {
					rkong.kongregate = kongregateAPI.getAPI();
					
					rkong.fake = false;
					rkong.status = 'ready';
				
					rat.console.log("kong API ready.");
					
					//	Init a few other rat kong-specific systems, if they exist.
					//	You can still choose to include a kong-specific module (like r_ads_kong) or not include it, but if it is included,
					//	now is a good time to initialize it.
					
					//	directly init user system
					rat.user.init(rkong);
					
					//	kong ads
					if (rat.ads && rat.ads.enableKong)
						rat.ads.enableKong();		//	why not automatic? probably ads system should listen for this.

					//	licensing (and purchase tracking)
					if (rat.license && rat.license.enableKong)
						rat.license.enableKong();
					
					//	queue or fire event saying the platform is ready.
					rat.events.queueEvent("platformReady", 'kong', null);
					
					rkong.focus();
				});
				
				//	and there's no way to detect an error.
				//	TODO: a timeout that does fake init if this fails?
				//	but it really shouldn't fail when really on kongregate.
				//	Yet it does?
			}
		}
		
		//	and if the above didn't work for any reason,
		//	then we're going to fake things.
		if (rkong.fake && rkong.status === 'init')
		{
			rkong.kongregate = null;
			rkong.fake = true;
			rkong.status = 'pending';
			
			rat.console.log("Kong: Faking it...");
			
			//	let's delay this a bit to more accurately fake delayed kong init.
			var timer = new rat.timer(function(arg)
				{
					rat.console.log("Delayed kong fake init");
					rkong.status = 'ready';
					rat.user.init(rkong);
				}, 0.2, null);
			timer.autoUpdate();
		}
	};
	
	rkong.isReady = function()
	{
		if (rkong.status === 'ready')
			return true;
		return false;
	};
	
	rkong.getUserId = function()
	{
		if (rkong.kongregate)
			return rkong.kongregate.services.getUserId();
		else
			return -1;
	};
	
	rkong.focus = function()
	{
		if (typeof(kongregateAPI) !== "undefined" && rkong.kongregate)
		{
			//	TODO:  Is this all needed, or just some of it?
			
			//rat.console.log("focus test z");
			if (window.top)
				window.top.focus();
			window.focus();
			rat.graphics.canvas.focus();
		}
	};
	
	//	hmm... doing this means we immediately set up kongregateAPI.loadAPI load call above,
	//	which quickly calls user setup code ... which means we should require user_kong module to be processed first
	//rkong.init();
	//	For the sake of compiled full versions of rat, don't auto-init kong.  We don't know if the game needs it.
	//	Expect the game to call rat.system.kong.init() if needed.
	
} );
//--------------------------------------------------------------------------------------------------
//
//	App license and in-app-purchase (IAP) management.
//	This module also includes some fake purchase support for testing IAP outside of an actual platform.
//
rat.modules.add("rat.os.r_license",
[
	{ name: "rat.os.r_user", processBefore: true },
	{ name: "rat.os.r_system", processBefore: true },
	{ name: "rat.os.r_events", processBefore: true },
	{ name: "rat.utils.r_timer", processBefore: true},

	{ name: "rat.os.r_license_winjs", platform: "winJS" },
	"rat.debug.r_console",
],
function (rat) {
	var license = {
		osIsLicensed: false,
		forceLicenseTo: void 0
	};
	rat.license = license;
	rat.License = license;	//	old naming for backcompat - should have been lowercase namespace, right?

	//	fake list of potentially purchasable stuff.
	//	Modeled after kongregate.  uses 0 means infinite use.  normal consumable stuff should have 1 use.
	var fakeCatalog = [
		{identifier:'frogbuddy', name:"Frog Buddy", description: "A frog buddy that will be your best friend forever", uses:0},
		{identifier:'acorn1', name:"Acorn", description: "A consumable acorn", uses:1},
	];
	var fakeInventory = [];	//	fake inventory of owned stuff.
	var fakeInventoryNextInstanceID = 1;

	//	Return if the game is owned
	license.isOwned = function () {
		if (license.forceLicenseTo !== void 0)
			return license.forceLicenseTo;
		else
			return license.osIsLicensed;
	};

	/** 	called when the License state changes
 */
	var lastOwned = false;
	license.onLicenseChange = function () {
		var owned = license.isOwned();
		if (lastOwned === owned)
			return;

		rat.events.fire("licenseChanged", owned, lastOwned);
		lastOwned = owned;
	};

	/** 	Force the game as licensed or not..
	   	an undefined value (void 0) means use real system state (don't force)
 */
	license.forceLicenseState = function (isOwned) {
		license.forceLicenseTo = isOwned;
		license.onLicenseChange();
	};
	/** 	unforce license state - the same as calling forceLicenseState(void 0), just a clearer way to do it.
 */
	license.unforceLicenseState = function () {
		license.forceLicenseState(void 0);
	};

	/** 	launch the purchase UI
 */
	license.launchPurchaseUI = function (forUserID) {
		forUserID = forUserID || rat.user.getActiveUserID();
		license._OSPurchaseUI(forUserID);
	};
	
	//	purchase one or more items at once,
	//	and call my callback when done.
	//	Should be overridden by real implementations.  This one is a generic fake API.
	license.purchaseItem = function(idList, metaData, callback)
	{
		//	fake it, with just the first item in the list.
		//	(todo: support fake-purchasing the full list)
		
		var timer = new rat.timer(function()
			{
				for (var i = 0; i < fakeCatalog.length; i++)
				{
					if (fakeCatalog[i].identifier === idList[0])
					{
						fakeInventory.push(
							//	this structure matches kong's requestUserItemList API
							{id:fakeInventoryNextInstanceID++,	//	instance id
							identifier:fakeCatalog[i].identifier,
							data:metaData,
							remaining_uses:fakeCatalog[i].uses}
						);
				
						rat.console.log("rat.license: fake purchase succeeded");
						callback({success:true});
						return;
					}
				}
				
				rat.console.log("rat.license: fake purchase failed");
				callback({success:false});
			}, 0.2, null);
		timer.autoUpdate();
	};
	
	//	request user item list.
	//	call callback when it's available.
	//	userName null for current player
	//	Should be overridden by real implementations.  This one is a generic fake API.
	license.getUserItemList = function(userName, callback)
	{
		//	simulation
		var timer = new rat.timer(function(arg)
			{
				rat.console.log("rat.license: delayed fake item list callback");
				callback({success:true, data:fakeInventory});
			}, 0.2, null);
		timer.autoUpdate();
	};
	
	//	consume an item, using the id from getUserItemList
	//	Should be overridden by real implementations.  This one is a generic fake API.
	license.consumeItem = function(instanceID, callback)
	{
		//	simulation
		var timer = new rat.timer(function(arg)
			{
				for (var i = 0; i < fakeInventory.length; i++)
				{
					if (fakeInventory[i].id === instanceID)
					{
						rat.console.log("rat.license: delayed fake item consume " + instanceID);
						if (fakeInventory[i].remaining_uses == 0)	//	(null or 0) infinite - not consumable.
							callback({success:false});
						else
						{
							fakeInventory[i].remaining_uses--;
							if (fakeInventory[i].remaining_uses <= 0)
							{
								fakeInventory.splice(i, 1);	//	delete it.
							}
						}

						callback({success:true, id:instanceID});
						return;
					}
				}
				
				callback({success:false});
				
			}, 0.2, null);
		timer.autoUpdate();
	},
	
	//	To add: some higher level functions:
	//		request simple count of a particular type of item (id list?) (e.g. consumables)
	//		consume N items of a particular type
	license.consumeItemType = function(id, count, callback)
	{
		//	consume this many of this type, using up multiple items if needed.
		//	e.g. if user has purchased a 10-gem pack and a 5-gem pack,
		//	we really want to just present that as 15 gems, and let them spend all 15 at once.
		//	potentially really complicated?  What if half the transaction fails?
		//	And do we return a list of ids consumed?
		//	At the very least, we need to pre-check to see if all exist in our known inventory.
		//	also, we probably need to request the latest inventory right now and act only when that's received.
		
		//	for now, let's just make this an easy way to consume an item by type instead of instance id.
		//	I'm implementing this on top of rat.license UI in the hope that it doesn't need to be reimplemented in platform-specific code.
		if (count > 1)
		{
			rat.console.log("rat.license: error: consumeItemType doesn't support count other than 1");
			return;
		}
		license.getUserItemList(null, function(result)
		{
			if (!result.success)
			{
				callback({success:false});
				return;
			}
			for (var i = 0; i < result.data.length; i++)
			{
				if (result.data[i].identifier === id)
				{
					license.consumeItem(result.data[i].id, callback);
					return;
				}
			}
			callback({success:false});
			return;
		});
	},
	
	/** 	NULL function for launching the purchase API
 */
	license._OSPurchaseUI = function (userID) {

		function buyGame() {
			license.osIsLicensed = true;
			license.onLicenseChange();
		}

		if (window.confirm) {
			var res = window.confirm("Buy the game?");
			if (res == true)
				buyGame()
		} else if (rat.system.has.winJS) {
			// Create the message dialog and set its content
			var msg = new Windows.UI.Popups.MessageDialog(
				"Buy the game?");

			// Add commands and set their command handlers
			var cmd = new Windows.UI.Popups.UICommand(
				"Yes",
				buyGame);
			cmd.Id = 0;
			msg.commands.append(cmd);

			cmd = new Windows.UI.Popups.UICommand(
				"No",
				function () { });
			cmd.Id = 1;
			msg.commands.append(cmd);

			msg.defaultCommandIndex = 0;
			msg.cancelCommandIndex = 1;
			msg.showAsync();

		}
	};
});
//--------------------------------------------------------------------------------------------------
//
//	App license management and IAP support for kongregate
//
rat.modules.add("rat.os.r_license_kong",
[
	{ name: "rat.os.r_license", processBefore: true },
	{ name: "rat.os.r_kong"},
],
function (rat) {
	
	//	I'm going to make this an explicit switch to turn on,
	//	because actually testing on real kong is a pain,
	//	and the built-in faking in r_license is useful when not actually running on kong,
	//	even for a kong-destined project.
	//	So, if kong is REALLY there, then call this function to turn on real kong licensing.
	//	TODO:  Do this with kong user system, too, instead of assuming it's there, if the module was included.
	
	var license = rat.license;
	var licenseKongEnabled = false;
	license.enableKong = function()
	{
		if (licenseKongEnabled)
			return;
		
		rat.console.log("rat.license: see if we can enable kong");
		
		if (typeof(kongregateAPI) === "undefined")
			return;
		var rkong = rat.system.kong;
		if (!rkong || !rkong.kongregate || !rkong.kongregate.mtx)
			return;
		
		rat.console.log("rat.license: Enabling Kong!");
		
		licenseKongEnabled = true;
		
		/** 	kong purchase ui?
		   	In the traditional sense (unlock this game), this doesn't exist.
		   	The only UI kong gives us is a kred purchase UI.
 */
		license._OSPurchaseUI = function (userID) {
			var ksys = rat.system.kong.kongregate.mtx;
			ksys.showKredPurchaseDialog("offers");
		};
		
		//	purchase one or more items at once,
		//	and call my callback when done.
		license.purchaseItem = function(idList, metaData, callback)
		{
			rat.console.log("L purchaseItem");
			
			//var ksys = rat.system.kong.kongregate.services;
			var ksys = rat.system.kong.kongregate.mtx;
			
			ksys.purchaseItems(idList, callback);
		};
		
		//	request user item list.
		//	call callback when it's available.
		//	userName null for current player
		license.getUserItemList = function(userName, callback)
		{
			rat.console.log("L getUserItemList");
			//var ksys = rat.system.kong.kongregate.services;
			var ksys = rat.system.kong.kongregate.mtx;
			
			ksys.requestUserItemList(userName, callback);
			//	translate results?  Currently, the rat API matches the kong api.
		};
		
		//	consume an item, using the id from getUserItemList
		license.consumeItem = function(instanceID, callback)
		{
			rat.console.log("L consumeItem");
			
			//var ksys = rat.system.kong.kongregate.services;
			var ksys = rat.system.kong.kongregate.mtx;
			
			ksys.useItemInstance(instanceID, callback);
		};
		
	};
});
//--------------------------------------------------------------------------------------------------
//
//	Advertising management.
//	This includes several kinds of ads, potentially, like banner ads and opt-in video ads.
//
//	This is the generic implementation that pretends like there are ads and they're being played.
//	include the proper module for the target OS you want, like r_ads_kong for kongregate.
//
//	TODO: Look at other Ad APIs and expand/improve this wrapper.
//
rat.modules.add("rat.os.r_ads",
[
	{ name: "rat.os.r_system", processBefore: true },
	{ name: "rat.os.r_events", processBefore: true },
	{ name: "rat.utils.r_messenger", processBefore: true},
	{ name: "rat.utils.r_timer", processBefore: true},
	"rat.debug.r_console",
],
function (rat) {
	var ads = {	//	ads namespace
		
		adPlaying : false,
		
		//	Note: all this "info" structure stuff is totally fake and placeholder,
		//	and I have no idea if it'll be useful later, but I wanted to make room.
		//	Feel free to add fields or use it how you like.
		curAdInfo : null,
		curAdTimer : null,
	};
	rat.ads = ads;
	rat.ads.messages = new rat.Messenger();
	var messageType = {
		AdsAvailable: "adsAvailable",
		AdsUnavailable: "adsUnavailable",
		AdOpened: "adOpened",
		AdCompleted: "adCompleted",
		AdAbandoned: "adAbandoned",
	};
	rat.ads.messageType = messageType;
	
	/** 	Return true if there are ads available, as far as we know.
 */
	ads.adsAvailable = function () {
		return true;
	};

	/** 	called when an ad is completed (and player should be rewarded)
 */
	ads.onAdCompleted = function (info) {
		ads.adPlaying = false;
		ads.curAdInfo = null;
		ads.curAdTimer = null;
		rat.ads.messages.broadcast("adCompleted", info);
	};
	
	/** 	called when an ad is abandoned
 */
	ads.onAdAbandoned = function (info) {
		ads.adPlaying = false;
		if (ads.curAdTimer)
			ads.curAdTimer.endAutoUpdate();
		ads.curAdInfo = null;
		ads.curAdTimer = null;
		rat.ads.messages.broadcast("adAbandoned", info);
	};
	
	/** 	get information about the ad that's currently playing.
	   	This implementation is unclear - right now, I think "time" is the only semi-dependable variable here...
 */
	ads.getAdPlaying = function()
	{
		if (ads.adPlaying)
		{
			if (ads.curAdTimer)
				ads.curAdInfo.time = ads.curAdTimer.currentTime;
			else
				ads.curAdInfo.time = 0;
			return ads.curAdInfo;
		}
		else
			return null;
	};

	/** 	start playing an ad video.
	   	returns ad info.
 */
	ads.openAd = function () {
		ads.adPlaying = true;
		
		ads.curAdInfo = {id:0};
		
		//	since this mdule fakes ads by default, set up a timer to end playback
		rat.console.log("fake ad: start");
		var timer = new rat.timer(function()
			{
				//	fake complete
				rat.console.log("fake ad: complete.");
				ads.onAdCompleted(ads.curAdInfo);
				
				//	fake abandoned
				//rat.console.log("fake ad: abandon.");
				//ads.onAdAbandoned(ads.curAdInfo);
			}, 2, null);
		timer.autoUpdate();
		ads.curAdTimer = timer;
		
		return ads.curAdInfo;
	};
	
	//	explicitly abandon this ad.
	//	This is not supported by some systems (e.g. kongregate), since that's a user choice.
	//	But it's useful at least for testing.
	ads.abandonAd = function () {
		if (ads.adPlaying && ads.curAdInfo)
		{
			ads.onAdAbandoned(ads.curAdInfo);
		}
	};
	
	/** 	mostly for debugging and faking ads
 */
	ads.renderFrame = function(ctx)
	{
		//	draw fake ad with countdown timer?
	};
	
	/** 	need update function?
 */
	
});
//--------------------------------------------------------------------------------------------------
//
//	Advertisement management for Kongregate API
//	Note that we aggressively fake this data when we're not actually running inside Kongregate,
//	for ease of development.
//
rat.modules.add( "rat.os.r_ads_kong",
[
	{ name: "rat.os.r_ads", processBefore: true},
	{ name: "rat.os.r_kong"},
	
	"rat.debug.r_console",
],
function(rat)
{
	rat.ads = rat.ads || {};
	
	var ads = rat.ads;
	var adsKongEnabled = false;
	ads.enableKong = function()
	{
		if (adsKongEnabled)	//	already enabled?
			return;
		rat.console.log("rat.ads: see if we can enable kong");
		
		if (typeof(kongregateAPI) === "undefined")
			return;
		var rkong = rat.system.kong;
		if (!rkong || !rkong.kongregate || !rkong.kongregate.mtx)
			return;
		
		rat.console.log("rat.ads: Enabling Kong!");
		
		adsKongEnabled = true;
		
		//	initialize some internal state
		ads.adsAreAvailable = false;	//	need to get event to tell us they're available
		
		//	turn on event listeners for kong ad events
		var mtx = rkong.kongregate.mtx;
		mtx.addEventListener("adsAvailable", function()
		{
			ads.adsAreAvailable = true;
		});
		mtx.addEventListener("adsUnavailable", function()
		{
			ads.adsAreAvailable = false;
		});
		mtx.addEventListener("adOpened", function()
		{
			ads.adPlaying = true;
		});
		mtx.addEventListener("adCompleted", function()
		{
			ads.onAdCompleted(ads.curAdInfo);
		});
		mtx.addEventListener("adAbandoned", function()
		{
			ads.onAdAbandoned(ads.curAdInfo);
		});
		
		//	now replace standard ads functions with kong ones.
		
		//	ads available?
		ads.adsAvailable = function()
		{
			return ads.adsAreAvailable;
		};
		
		//	start playing an ad
		ads.openAd = function () {
			rat.console.log("kong ad: openAd");
			ads.curAdInfo = {id:0};
			mtx.showIncentivizedAd();
			return ads.curAdInfo;
		};
		
		//	and turn on the system
		mtx.initializeIncentivizedAds();
	};
} );
//--------------------------------------------------------------------------------------------------
//
//	Input map class for handling gamepad/keyboard navigation of on-screen buttons
//		(and other targetable enabled interactive ui elements)
//
//  To support this mapping in your own targetable input elements,
//	your ui class needs to implement an interface that allows the following functions:
//      Press() - button got pressed (user hit enter)
//      Focus() - button is now the target.  returns boolean as to whether it succeeded or not
//      Blur() -- button is no longer the target.
//
//  Each entry in the inputMap needs to include a reference to that button object itself.
//		I named this 'currObj'
//
//  Example code for creating a mapping:
//
//  // there are two buttons 'back', and 'ads'
//  var buttonBack = { currObj: this.backButton, down: this.adsButton, right: this.adsButton }
//  var buttonAds = { currObj: this.adsButton, up: this.backButton, left: this.backButton }
//  var map = [buttonBack, buttonAds]
//
//  You can auto-build inputmaps for a screen by using the functions in the rat.Screen class.
//
//	TODO - really nothing external should know about the index,
//		so the 'startIndex' should probably be changed in the future to 'startButton'
//			and have it save the index based on the object it finds.
//		Maybe remove the concept of a current index entirely.
//			If we need to support multi-target systems (e.g. multiple controllers, like Wraith does),
//			it'll all have to change anyway, and a cTargets system like Wraith might be more flexible.
//	TODO:  rename press/focus/blur functions to not have capital names.
//	TODO:  Not everything in an input map has to be a "button"
//	TODO:  Tab and shift-tab support
//	TODO:  Automated building of inputMaps based on contents of a screen
//	TODO:  non-buttons!  checkboxes, editboxes, etc.
//			rename everything to "elements" instead of buttons.
//

//------------ rat.InputMap ----------------
rat.modules.add( "rat.input.r_inputmap",
[
	"rat.input.r_input"
], 
function(rat)
{
	/**
	 * @constructor
	 */
	rat.InputMap = function (map, startIndex) {
		this.events = {};
		this.map = map;

		if ((!startIndex || startIndex === 0) && this.checkBounds(startIndex) && this.map[startIndex]) {
			this.defaultIndex = startIndex;
			this.index = startIndex;
		}
		else {
			this.defaultIndex = -1;
			this.index = -1;
		}

		this.validateMap();
		
		this.navigateSound = rat.InputMap.defaultNavigateSound;

		if (this.index >= 0)
			this.focusButton(this.index, true);
	};
	
	//	assigned to all future inputmaps
	rat.InputMap.defaultNavigateSound = void 0;

	/** 	Set the on change event callback
 */
	rat.InputMap.prototype.setOnChange = function (cb) {
		this.events.onChange = cb;
	};

	//	run through input map we were given, and fix up any problems.
	//	Why?  To make defining input maps easier and less error prone.
	//	For instance, we might define an input map that refers to a button that may or may not be there.
	//	Instead of having complex input map creation logic, we'll just leave all our up/down/left/right references,
	//	but just not include it in the map, and fix that here.
	//	This would be less necessary if we used automated input map generation more often.
	rat.InputMap.prototype.validateMap = function()
	{
		var dIndex;
		for (var i = 0; i < this.map.length; ++i)
		{
			var curEntry = this.map[i];
			var directions = ["up", "left", "right", "down", "select"];
			for (dIndex = 0; dIndex < directions.length; dIndex++)
			{
				var target = curEntry[directions[dIndex]];	//	TODO: this (and similar index-by-name code below) will fail with closure compiler)
				if (target)
				{
					var found = null;
					for (var tIndex = 0; tIndex < this.map.length; ++tIndex)
					{
						if (this.map[tIndex].currObj === target)
							found = target;
					}
					if (!found)
						curEntry[directions[dIndex]] = null;	//	clear that bogus reference out
				}
			}	//	end of direction loop
		}
	};

	rat.InputMap.prototype.handleKeyDown = function (keyCode) {
		var direction;
		// may also add directions for controller inputs?

		switch (keyCode) {
			case rat.keys.leftArrow:
				direction = "left";
				break;
			case rat.keys.upArrow:
				direction = "up";
				break;
			case rat.keys.rightArrow:
				direction = "right";
				break;
			case rat.keys.downArrow:
				direction = "down";
				break;
			case rat.keys.enter:
				direction = "select";
				break;
			default:
				return;
		}

		return this.handleDirection(direction);
	};

	//	Handle direction input - navigate through targetable elements
	rat.InputMap.prototype.handleDirection = function (direction) {
		if (this.checkBounds(this.index)) {
			var currentButton = this.map[this.index];

			if (direction === "select")
				this.doPress(this.map[this.index].currObj);
			else if (currentButton && currentButton[direction] && currentButton.currObj !== currentButton[direction]) {
				//var oldIndex = this.index
				var newIndex;
				// get handle to new button
				var newfocus = currentButton[direction];
				// find index for new handle
				for (var i = 0; i < this.map.length; ++i) {
					if(this.map[i].currObj === newfocus)
						newIndex = i;
				}
				//	on directional movement, optionally play a sound.
				if (this.navigateSound && rat.audio)
					rat.audio.playSound(this.navigateSound);
					
				this.focusButton(newIndex);
				return true;
			}
			else if (currentButton)
			{
				// wasn't a direction to go to, rehighlight self
				//	But tell the button about the direction
				this.doFocus(currentButton.currObj);
				if (currentButton.currObj.handleDirection)
				{
					if (currentButton.currObj.handleDirection(direction))
						return true;
				}
			}
		}
		else if (this.index === -1)   // was currently not a valid index
		{
			// then set the index to default
			if(this.defaultIndex === -1 && this.map.length > 0)
				this.index = 0;
			else if(this.checkBounds(this.defaultIndex))
				this.index = this.defaultIndex;

			if (this.checkBounds(this.index))
			{
				this.doFocus(this.map[this.index].currObj);
				return true;
			}
		}

		return false;
	};
	
	//	Handle direction input - tab or reverse-tab through targetable elements
	rat.InputMap.prototype.handleTab = function (event) {
		
		var delta = 1;
		if (event.sysEvent.shiftKey)
			delta = -1;
		
		if (this.checkBounds(this.index)) {
			var currentButton = this.map[this.index];
			
			var newIndex = 0;
			
			if (currentButton.tabOrder !== void 0)
			{
				newOrder = currentButton.tabOrder + delta;
				//	wrap
				if (newOrder < 0)
					newOrder = this.map.length-1;
				else if (newOrder > this.map.length-1)
					newOrder = 0;
				
				//	find the entry with that desired tab order
				for (var i = 0; i < this.map.length; ++i) {
					if (this.map[i].tabOrder === newOrder)
					{
						newIndex = i;
						break;
					}
				}
			}
			else
			{
				newIndex = this.index + delta;
				//	wrap
				if (newIndex < 0)
					newIndex = this.map.length-1;
				else if (newIndex > this.map.length-1)
					newIndex = 0;
			}
			
			this.focusButton(newIndex);
			
			if (this.index === newIndex)	//	success?
			{
				var elem = this.map[newIndex].currObj;
				if (elem.handleTabbedTo)
					elem.handleTabbedTo(event);	//	let some elements handle this more specifically
			}

			return true;
		}
		
		return false;
	};

	//	wrapper for setting button focus
	//	currently, helps us use new function names,
	//	but could also be used later to do fancier focus handling with target system
	rat.InputMap.prototype.doFocus = function(button)
	{
		if (button.Focus)
			return button.Focus();
		else if (button.focus)
			return button.focus();
		else
			return false;
	};

	//	wrapper for setting button blue
	//	currently, helps us use new function names,
	//	but could also be used later to do fancier focus handling with target system
	rat.InputMap.prototype.doBlur = function(button)
	{
		if (button.Blur)
			return button.Blur();
		else if (button.blur)
			return button.blur();
		else
			return false;
	};

	//	wrapper for setting button pressing
	//	currently, helps us use new function names,
	//	but could also be used later to do fancier focus handling with target system
	rat.InputMap.prototype.doPress = function(button)
	{
		if (button.Press)
			return button.Press();
		else if (button.press)
			return button.press();
		else
			return false;
	};
	
	rat.InputMap.prototype.getButtonCount = function () {
		return this.map.length;
	};

	rat.InputMap.prototype.getButton = function (index) {
		if (this.checkBounds(index))
			return this.map[index].currObj;
		return null;
	};

	rat.InputMap.prototype.getCurrIndexButton = function ()
	{
		if(this.checkBounds(this.index))
			return this.map[this.index].currObj;
		return null;
	};
	//	a slightly more consistent name for that...
	rat.InputMap.prototype.getCurrentFocusedButton = rat.InputMap.prototype.getCurrIndexButton;
	//	an even better name.  :)
	rat.InputMap.prototype.getTarget = rat.InputMap.prototype.getCurrIndexButton;
	
	//	return the current target element.
	//	The reason we take ratEvent as an argument here is so we can do things like support multiple targets,
	//	or check what kind of event it is and filter out some possible targets?
	rat.InputMap.prototype.getTargetForEvent = function(ratEvent)
	{
		return this.getTarget();
	};

	//	get the index for this button.
	//	if not found, return null (which is weird?)
	rat.InputMap.prototype.getIndexByButton = function (button) {
		for (var i = 0; i < this.map.length; ++i) {
			if (this.map[i].currObj === button)
				return i;
		}
		return null;
	};

	//
	//	Focus on this button by index.
	//	"force" is a way to force the new button to get a focus() call, even if it's already the currrent.
	//	Also blur the old button
	/** 
	 * @param {number|undefined} index
	 * @param {boolean=} force
	 */
	rat.InputMap.prototype.focusButton = function (index, force) {
		var oldIndex = this.index;

		if (index === this.index)   //  already at this index
		{
			if (force)  //  but support forcing anyway
				oldIndex = -1;  //  in which case don't blur!
			else
				return;
		}

		if (this.checkBounds(index) && this.map[index]) {
			if (this.doFocus(this.map[index].currObj))
			{
				//  blur old button
				if (this.checkBounds(oldIndex) && this.map[oldIndex])
					this.doBlur(this.map[oldIndex].currObj);
			}
			this.index = index;

			if (this.events.onChange)
				this.events.onChange(this.map[index].currObj, this.map[oldIndex] ? this.map[oldIndex].currObj : void 0);
		}
	};
	
	//
	//	Focus on this button by button reference.
	//	"FocusByIndex" would be a better name for the above function.
	//
	rat.InputMap.prototype.focusByButton = function (button, force) {
		var index = this.getIndexByButton(button);
		if (typeof(index) === null)
			return;
		this.focusButton(index, force);
	};

	rat.InputMap.prototype.clearIndex = function ()
	{
		this.index = -1;
	};

	rat.InputMap.prototype.reset = function ()
	{
		if(this.index !== this.defaultIndex)
		{
			if(this.checkBounds(this.index))
				this.doBlur(this.map[this.index].currObj);
			if(this.checkBounds(this.defaultIndex))
				this.doFocus(this.map[this.defaultIndex].currObj);

			this.index = this.defaultIndex;
		}
	};

	//	check this index - see if it's really in our list of input controls.
	//	(return false if not)
	rat.InputMap.prototype.checkBounds = function (index)
	{
		if(index >= 0 && index < this.map.length)
			return true;
		return false;
	};
	
	//	debug - dump list of elements we've collected
	rat.InputMap.prototype.dumpList = function (index)
	{
		rat.console.log("inputmap: " + this.map.length + " elements:");
		for (var i = 0; i < this.map.length; ++i)
		{
			var curEntry = this.map[i];
			var elem = curEntry.currObj;
			var name = elem.name || "(no name)";
			var type = "type: " + (elem.elementType || "?");
			rat.console.log("" + i + " : " + name + " " + type);
		}
	};
} );
//--------------------------------------------------------------------------------------------------
//
//	Support for creating UI elements from data
//
//
/*
	Usage:
	
	AS TREE (a top-level node, with hierarchy under that)
		
		Use createTreeFromData() inside a screen constructor,
		like this:
		
		screen.setSize(rat.graphics.SCREEN_WIDTH, rat.graphics.SCREEN_HEIGHT);	//	or whatever
		screen.setPos(0, 0);	//	or whatever
		var panes = rat.ui.data.createTreeFromData({
			... top level node with subnodes
		}
		, screen.getBounds());
		screen.appendSubElement(panes);
		
		the format of the data is hierarchical,
		with each entry having a "children" array of subelements.
		
		The above usage assumes a top-level node that contains everything else.
	
	AS ARRAY (a list of panes, with hierarchy under those)
	
		Alternatively, you can use an array without a top-level node, like this:
		
		rat.ui.data.createChildrenFromData(screen, [
			... array of nodes with subnodes
		]);
		
		This might be simpler.
	
	PRELOADING:
		rat.ui.data.preLoadAssetsFromData(data);
*/
		
rat.modules.add( "rat.ui.r_ui_data",
[
	{name: "rat.utils.r_utils", processBefore: true },
	{name: "rat.ui.r_ui", processBefore: true },
	
	"rat.debug.r_console",
	"rat.utils.r_shapes",
], 
function(rat)
{
	//	Namespace for all of this functionality
	rat.ui.data = {};
	
	rat.ui.data.inDesignTime = false;	//	set this flag to allow designTimeOnly panes to be created!

	function calcSizeFromFlags(val, parent)
	{
		if (val.val !== void 0)
		{
			if (val.percent)
				val.val *= parent;
			if (val.fromParent)
				val.val += parent;
			val = val.val;
		}
		return val;
	}

	/** @suppress {missingProperties} */
	function calcPosFromFlags(val, parentSize, mySize)
	{
		if (val.val === void 0)
			val = { val: val };

		{
			//	Percentage of parent size
			if (val.percent)
				val.val *= parentSize;

			//	From the center of the parent
			if (val.fromCenter)
				val.val += (parentSize / 2);
				//	From the edge of the parent
			else if (val.fromParentEdge || val.fromParentFarEdge)
				val.val = parentSize - val.val;

			//	centered (not the same as setting center values, see elsewhere)
			if (val.centered || val.drawCentered || val.center)
				val.val -= mySize / 2;
			else if (val.fromMyEdge || val.fromMyFarEdge)
				val.val -= mySize;
			val = val.val;
		}
		return val;
	}

	// Calculate bounds based on flags, data, and parent bounds
	//	Supported flags
	//	noChange
	//	autoFill
	//	Per size field flags
	//		percent
	//		fromParent
	//	Per pos field flags
	//		percent
	//		center
	rat.ui.data.calcBounds = function (data, parentBounds)
	{
		var bounds = rat.utils.copyObject(data.bounds || {}, true);
		bounds.x = bounds.x || 0;
		bounds.y = bounds.y || 0;
		bounds.w = bounds.w || 0;
		bounds.h = bounds.h || 0;


		//	No change flag set
		if (bounds.noChange)
		{
			if (bounds.x.val !== void 0)
				bounds.x.val = bounds.x.val;
			if (bounds.y.val !== void 0)
				bounds.y.val = bounds.y.val;
			if (bounds.w.val !== void 0)
				bounds.w.val = bounds.w.val;
			if (bounds.h.val !== void 0)
				bounds.h.val = bounds.h.val;
			return bounds;
		}

		//	Auto fill always auto-fills
		if (bounds.autoFill)
			return { x: 0, y: 0, w: parentBounds.w, h: parentBounds.h };

		//	Find the size I will be.
		bounds.w = calcSizeFromFlags(bounds.w, parentBounds.w);
		bounds.h = calcSizeFromFlags(bounds.h, parentBounds.h);
		bounds.x = calcPosFromFlags(bounds.x, parentBounds.w, bounds.w);
		bounds.y = calcPosFromFlags(bounds.y, parentBounds.h, bounds.h);
		
		return bounds;
	};
	
	//	Given a data type code,
	//	figure out what constructor class to use, and what setupFromData function.
	//	This is intented to be flexible and easy - if something is missing, figure out a decent substitute,
	//	like parent class, or Element class.
	rat.ui.data.getSetupCallsForType = function( dataType )
	{
		var paneType = dataType;
		if (paneType === "Container")
			paneType = "Element";
		if( !rat.ui[paneType] )
		{
			rat.console.log( "WARNING! Unknown pane type '" + paneType + "' hit in createPaneFromData.  Falling back to Element." );
			paneType = "Element";
		}
		
		//	Find the create function, falling back to parent types if we need to.
		var elementClass = rat.ui[paneType];
		//	If we did not find anything, then use the the Element one as we know that it does (or atleast should) exist
		if (!elementClass)
		{
			rat.console.log("WARNING! Unable to find createFromData for element of type " + paneType + ".  Reverting to rat.ui.Element.createFromData");
			elementClass = rat.ui.Element;
		}

		//	get the setupFromData func
		var setupClass = elementClass;
		while (setupClass && !setupClass.setupFromData)
		{
			if (setupClass.prototype)
				setupClass = setupClass.prototype.parentConstructor;
			else
				setupClass = void 0;
		}
			
		//	If we did not find anything, then use the the Element one as we know that it does (or atleast should) exist
		if (!setupClass)
		{
			rat.console.log( "WARNING! Unable to find createFromData for element of type "+paneType+".  Reverting to rat.ui.Element.createFromData" );
			setupClass = rat.ui.Element;
		}
		
		return {paneType:paneType, elementClass:elementClass, setupClass:setupClass};
	};

	//	Create any pane from data
	//	It's possible for this to return null, if the data was explicitly marked as being not constructable...
	rat.ui.data.createPaneFromData = function( data, parent )
	{
		if (data._editor && data._editor.designTimeOnly)
			return null;
		
		var parentBounds;
		if( parent )
			parentBounds = parent.getBounds();
		else
			parentBounds = {x: 0, y:0, w:0, h:0};

		//	find constructor and setup functions
		var setups = rat.ui.data.getSetupCallsForType(data.type);
		
		//	Create it.
		var pane = new setups.elementClass();
		if (setups.setupClass.setupFromData)
			setups.setupClass.setupFromData(pane, data, parentBounds);

		//	Now create its children
		rat.ui.data.createChildrenFromData(pane, data.children);

		//	Call any onCreate callback
		if (data.onCreate)
			data.onCreate(pane);
		return pane;
	};

	/**  Create the children of an element
 */
	rat.ui.data.createChildrenFromData = function (parent, children)
	{
		if (!parent)
			return;
		if (!children)
			return;
		for (var index = 0; index !== children.length; ++index)
		{
			var pane = rat.ui.data.createPaneFromData(children[index], parent);
			if (pane)
				parent.appendSubElement(pane);
		}
	};

	// Call setupFromData on this type's parent type
	//	(so we can walk up the inheritance tree properly setting up all properties)
	rat.ui.data.callParentSetupFromData = function (type, pane, data, parentBounds)
	{
		var setupClass = type;
		do
		{
			if (setupClass)
			{
				if (setupClass.prototype)
					setupClass = setupClass.prototype.parentConstructor;
				else
					setupClass = void 0;
			}
		} while (setupClass && !setupClass.setupFromData);

		//	If we did not find anything there is nothing else to call
		if (!setupClass)
			return;
		setupClass.setupFromData(pane, data, parentBounds);
	};
	
	//	A function that will create a given graphical structure from "JSON" data
	//	Note that in some cases, we may violate the strict JSON structure by allowing functions 
	/**
	 * @param {Object} data
	 * @param {Object=} bounds
	 */
	rat.ui.data.createTreeFromData = function (data, bounds)
	{
		//	Create the root pane
		var parent = {
			getBounds: function(){
				if (bounds)
					return bounds;
				else
					return new rat.shapes.Rect(0, 0, rat.graphics.SCREEN_WIDTH, rat.graphics.SCREEN_HEIGHT);
			}
		};
		return rat.ui.data.createPaneFromData(data, parent);
	};
	
	//	
	//	Preload any assets referenced in any subpane of this tree/array.
	//	The "data" argument here can either be an object or an array.
	rat.ui.data.preLoadAssetsFromData = function (data)
	{
		//	our approach here is to walk through the whole tree here, recursively,
		//	looking for anything that "looks like" an asset reference.
		//	an alternative would be to kinda recreate all the above, and call "preLoadPaneFromData" functions, or something
		//	but what a pain that would be!
		//	Note that this will result in duplicates (an asset that's referred to several times will be listed several times)
		//	and that's OK - the graphics preloader won't load them more than once.
		
		var list = [];
		
		//	a subfunction to do the actual work.
		//	could have been this same func, but it's so often that we need this utility to be different
		//	from the public API that triggers it...
		var searchFunc = function(o)
		{
			if (typeof (o) !== "object" || o === null)
				return;

			for (var key in o)
			{
				var val = o[key];
				//	Skip undefined fields
				if (val !== void 0 && o.hasOwnProperty(key))
				{
					//	TODO: see if key name itself looks like an asset reference,
					//	like "image" or "asset" or "resource"?
					//	or have some registered list somewhere?
					
					if (typeof (val) === "string")
					{
						var sub = val.substring(val.length-4).toLowerCase();
						if (sub === ".png" || sub === ".jpg" || sub === ".gif" || sub === ".svg" || sub === ".bmp" || sub === ".ico")
						{
							//console.log("found to preload: " + val);
							list.push(val);
						}
					} else if (typeof(val) === "object")	//	includes arrays!
					{
						searchFunc(val);
					}
				}
			}
		};
		
		if (list.length > 0)
			rat.graphics.preLoadImages(list);
		
		searchFunc(data);
	};
	
} );
//--------------------------------------------------------------------------------------------------
//
//	Word wrapping utils
//	This can get quite complicated depending on the language.
//	So, for now, just supporting English.
//	Later, see http://en.wikipedia.org/wiki/Word_wrap
//	and http://en.wikipedia.org/wiki/Line_breaking_rules_in_East_Asian_language
//	and http://msdn.microsoft.com/en-us/goglobal/bb688158.aspx , which is a pretty good reference.
//	and we might need to change canBreakBetween to take different arguments.
//		e.g. what if we think we can break after a space because the two chars are ' ' and 'T', but BEFORE that,
//		there was a non-allowed end-of-line character (e.g. '*' in chinese)?
//		Though, why would there be a space after non-allowed-end-of-line?  Maybe we can ignore that and assume breaking after a space is always OK.
rat.modules.add( "rat.utils.r_wordwrap",
[
	"rat.debug.r_console",
], 
function(rat)
{
	rat.wordwrap = {

		isWhitespace : function(char)
		{
			//	these characters we consider whitespace.  Are there more?
			//	we should not count non-breaking space as whitespace (0x00a0)
			//	0x3000 is ideographic space
			return (char === ' ' || char === '\t' || char === '\n' || char === '\r' || char === String.fromCharCode(0x3000));
		},

		skipWhitespaceAhead : function(string, pos)
		{
			for (; pos < string.length; pos++)
			{
				if (!rat.wordwrap.isWhitespace(string.charAt(pos)))
					return pos;
			}
			return pos;
		},
		
		//	We need to be able to say generally don't break up Latin/Cyrillic words,
		//	but DO break up anything else.  So, for now, let's try this.  Anything alphabetic should not be broken up.
		//	note: greek is in 0x3FF range
		isAlphabetic : function(charCode)
		{
			return (charCode < 0x1000);
		},

		//	French has annoying rules about two-part punctuation where a space preceeds a mark, like " ?"
		isTwoPartPunctuation : function(charX)
		{
			return (charX === '?' || charX === '!' || charX === ';' || charX === ':');
		},
		
		//	OK, basically, we're going to allow wrapping anywhere there's a whitespace or a hyphen
		//	don't bother checking if char1 is whitespace, since we should have already marked this as breakable when char2 was whitespace
		
		//	TODO:  For french, we need to look farther forward.  :(
		//	e.g. we may be testing "x ", but we can't break there if the next character is "?".
		//	Change this to pass in a full string and offset, or something?
		//	also read up on multi-part wrapping - is there a rule for "! "?
		canBreakBetween : function(char1, char2)
		{
			var code1 = char1.charCodeAt(0);
			var code2 = char2.charCodeAt(0);
			
			//	let's start with the idea that we should not break
			var canBreak = false;
			//	but if we're dealing with CJK, the general rule is it's OK to break anywhere
			if (!rat.wordwrap.isAlphabetic(code1) || !rat.wordwrap.isAlphabetic(code2))
				canBreak = true;
			
			//	or if we're whitespace or hyphen
			if (rat.wordwrap.isWhitespace(char2) || char1 === '-')
				canBreak = true;
			
			//	except for a bunch of exceptions!
			//	I adapted what we've learned from XBLA games.
			//	I haven't explicitly checked these rules against the various online docs.
			if (rat.wordwrap.isNonBeginningChar(code2) ||
					rat.wordwrap.isNonEndingChar(code1)
					//|| (char1 === ' ' && rat.wordwrap.isTwoPartPunctuation(char2))
					//|| (char2 === ' ' && rat.wordwrap.isTwoPartPunctuation(char1))
			)
				canBreak = false;
			
			return canBreak;
		},

		//
		//	break this string into an array of lines, based on various word wrapping rules.  What fun!
		//	It is assumed that the given context is set up correctly with the font we care about.
		//	We do not hyphenate.  If your line simply can't be broken up, we just chop it wherever (currently, after the first char).
		//
		//	return the array of lines.
		wrapString : function(string, maxWidth, ctx)
		{
			var lines = [];
			var curStart = 0;
			var lastBreakEnd = 0;	//	end of line leading up to break
			var lastBreakStart = 0;	//	start of line after break (might not be the same if there's whitespace, which we eat up)

			//	I think this approach is reasonable and robust:
			//	walk through the string.  remember last breakable point.
			//	When string gets bigger than max width, go back and break at last breakable point.
			//	If there was no good breakable point, break where we got too wide.
			//		We have to break *somewhere* rather than go out of bounds, e.g. into non-title-safe area
			
			//	utility to accept this one line into the list of lines
			function takeLine(start, end)
			{
				lines.push(string.substring(start, end));
				//console.log("breaking at " + end + " : " + lines[lines.length-1]);
			}

			//	walk through full text
			for (var i = 0; i < string.length; i++)
			{
				//	always break on manual breaks...
				if (string[i] === '\n')
				{
					takeLine(curStart, i);
					curStart = i+1;	//	skip \n
					lastBreakEnd = lastBreakStart = curStart;	//	just start with some reasonable defaults
					continue;
				}

				//	check if this character puts us over the limit, and we need to break first
				var testString = string.substring(curStart, i+1);
				var metrics = ctx.measureText(testString);
				var testWidth = metrics.width || metrics;
				if (testWidth >= maxWidth)
				{
					if (lastBreakEnd === curStart)	//	we never found a good breaking place!
					{
						//	we are forced to just cut off somewhere else, rather than overlap our bounds.
						//	This is a case where the content creator must change the text or the space we have to work with.
						//	Cut off right before this new character, since it's what put us over.
						rat.console.logOnce("bad textwrap at " + i);
						lastBreakEnd = lastBreakStart = i;
					}
					takeLine(curStart, lastBreakEnd);
					curStart = lastBreakEnd = lastBreakStart;
				}

				//	now check if we can break after this character, and remember that for future use.
				if (i === string.length-1 || //	always can break after final char, and it won't matter - just short-circuit next expression
						rat.wordwrap.canBreakBetween(string.charAt(i), string.charAt(i+1))	//	OK to break here
					)
				{
					lastBreakEnd = i+1;
					//	if we CAN break here, then skip ahead past any whitespace, so we don't start a new line with empty space.
					lastBreakStart = rat.wordwrap.skipWhitespaceAhead(string, i+1);
				}
			}

			//	pick up the rest of the final line, if any
			if (curStart !== string.length)
			{
				takeLine(curStart, string.length);
			}

			return lines;
		},
		
		//	these characters are not allowed to begin a line
		//	c is charCode, not character or string
		//	TODO: These tables don't seem to match http://msdn.microsoft.com/en-us/goglobal/bb688158.aspx
		isNonBeginningChar : function (c)
		{
			return (
				c === 0x0045 ||		//	no hyphens at start of line
				c === 0x00a0 ||		//	no non-breaking spaces at the start of a line
				c === 0xff0c ||		// 
				c === 0xff0e ||		// 
				c === 0xff1a ||		// 
				c === 0xfe30 ||		// 
				c === 0xfe50 ||		// 
				c === 0x00B7 ||		// 
				c === 0xfe56 ||		// 
				c === 0xff5d ||		// 
				c === 0x300F ||		// 
				c === 0xfe5c ||		// 
				c === 0x201D ||		// 
				c === 0x003A ||		// 
				c === 0x005D ||		// 
				c === 0x2022 ||		// 
				c === 0x2027 ||		// 
				c === 0x2026 ||		// 
				c === 0xfe51 ||		// 
				c === 0xfe54 ||		// 
				c === 0xfe57 ||		// 
				c === 0x3011 ||		// 
				c === 0x300D ||		// 
				c === 0xfe5e ||		// 
				c === 0x301E ||		// 
				c === 0x003B ||		// 
				c === 0x007D ||		// 
				c === 0xff1b ||		// 
				c === 0xff01 ||		// 
				c === 0xfe52 ||		// 
				c === 0xfe55 ||		// 
				c === 0x3015 ||		// 
				c === 0x3009 ||		// 
				c === 0xfe5a ||		// 
				c === 0x2019 ||		// 
				c === 0x2032 ||		// 
				c === 0x0025 ||		// 
				c === 0x00B0 ||		// 
				c === 0x2033 ||		// 
				c === 0x2103 ||		// 
				c === 0x300b ||		// 
				c === 0xff05 ||		// 
				c === 0xff3d ||		// 
				c === 0xffe0 ||		// 
				c === 0x2013 ||		// 
				c === 0x2014 ||		// 
				c === 0xff61 ||		// 
				c === 0xff64 ||		// 
				c === 0x3063 ||		// 
				c === 0x3083 ||		// 
				c === 0x3085 ||		// 
				c === 0x3087 ||		// 
				c === 0x30c3 ||		// 
				c === 0x30e3 ||		// 
				c === 0x30e5 ||		// 
				c === 0x30e7 ||		// 
				c === 0x3041 ||		// 
				c === 0x3043 ||		// 
				c === 0x3045 ||		// 
				c === 0x3047 ||		// 
				c === 0x3049 ||		// 
				c === 0x308e ||		// 
				c === 0x30a1 ||		// 
				c === 0x30a3 ||		// 
				c === 0x30a5 ||		// 
				c === 0x30a7 ||		// 
				c === 0x30a9 ||		// 
				c === 0x30ee ||		// 
				c === 0x0022 ||		// Quotation mark
				c === 0x0021 ||		// Exclamation mark
				c === 0x0029 ||		// Right parenthesis
				c === 0x002c ||		// Comma
				c === 0x002e ||		// Full stop (period)
				c === 0x003f ||		// Question mark
				c === 0x3001 ||		// Ideographic comma
				c === 0x3002 ||		// Ideographic full stop
				c === 0x30fc ||		// Katakana-hiragana prolonged sound mark
				c === 0xff01 ||		// Fullwidth exclamation mark
				c === 0xff09 ||		// Fullwidth right parenthesis
				c === 0xff1f ||		// Fullwidth question mark
				c === 0xff70 ||		// Halfwidth Katakana-hiragana prolonged sound mark
				c === 0xff9e ||		// Halfwidth Katakana voiced sound mark
				c === 0xff9f
			);		// Halfwidth Katakana semi-voiced sound mark
		},
		
		//	these characters are not allowed to end a line
		//	c is charCode, not character or string
		isNonEndingChar : function(c)
		{
			return (
				c === 0x3010 ||		// 
				c === 0x300C ||		// 
				c === 0xfe5d ||		// 
				c === 0x301D ||		// 
				c === 0x005B ||		// 
				c === 0x3014 ||		// 
				c === 0x3008 ||		// 
				c === 0xfe59 ||		// 
				c === 0x2018 ||		// 
				c === 0x2035 ||		// 
				c === 0x007B ||		// 
				c === 0xff5b ||		// 
				c === 0x300E ||		// 
				c === 0xfe5b ||		// 
				c === 0x201C ||		// 
				c === 0x005C ||		// 
				c === 0x0024 ||		// 
				c === 0xffe1 ||		// 
				c === 0x300A ||		// 
				c === 0xff04 ||		// 
				c === 0xff3b ||		// 
				c === 0xffe6 ||		// 
				c === 0xffe5 ||		// 
				c === 0x0022 ||		// Quotation mark
				c === 0x0028 ||		// Left parenthesis
				c === 0xff08		// Fullwidth left parenthesis
			);
		},
	};
	
} );
//--------------------------------------------------------------------------------------------------
//	bubblebox ui element
//	renders with a loaded image
//	maybe make a subclass of sprite to inherit image loading/handling?
rat.modules.add( "rat.ui.r_ui_bubblebox",
[
	{name: "rat.utils.r_utils", processBefore: true },
	{name: "rat.ui.r_ui", processBefore: true },
	
	"rat.math.r_vector",
	"rat.graphics.r_image",
	"rat.debug.r_console",
	"rat.graphics.r_graphics",
], 
function(rat)
{
	// NOTE: these definitions and settings allow us to draw differently (and faster) if the tiles are solid and stretchable
	// when manually doing lots of draw calls for tiling it can create significant performance problems on some hardware
	rat.ui.BubbleBox_TILE = 0;
	rat.ui.BubbleBox_STRETCH = 1;

	// the function rat.ui.BubbleBox_setDefaultDrawType(drawType) can be used to set this if desired
	rat.ui.BubbleBox_defaultDrawType = rat.ui.BubbleBox_TILE;

	/**
	 * @param {string=} resource
	 * @param {number=} drawType How does this bubble box draw (stretch/tile)
	 * @constructor
	 * @extends rat.ui.Element
	 */
	rat.ui.BubbleBox = function (resource, drawType)
	{
		rat.ui.BubbleBox.prototype.parentConstructor.call(this); //	default init
		this.blockSize = new rat.Vector(4, 4);	//	placeholder 'till we get our image
		this.resource = resource;
		if (resource !== void 0)
		{
			this.imageRef = new rat.graphics.ImageRef(resource);
			var self = this;
			this.imageRef.setOnLoad(function (img)
			{
				self.updateWithNewImageSize(img.width, img.height);
			});
		}
		this.name = "<bbl>" + resource + this.id;

		if (drawType)
			this.drawType = drawType;
		else
			this.drawType = rat.ui.BubbleBox_defaultDrawType;
		//	note that bubblebox is often used as a group, with stuff inside it, so don't turn off trackmouse
	};
	rat.utils.inheritClassFrom(rat.ui.BubbleBox, rat.ui.Element);
	rat.ui.BubbleBox.prototype.elementType = "bubbleBox";

	//	update internal calculations with newly loaded image size
	//	(e.g. after load finishes)
	rat.ui.BubbleBox.prototype.updateWithNewImageSize = function (width, height)
	{
		//	detect if the image is a 3x3 or a 4x4, in order to be more flexible.  Assume even pixel size will tell us...
		//	The 4x4 format is something we used in wraith.
		//	TODO: support the 9-tile format we've seen out there, where center spaces are bigger than outer spaces?
		var div = 4;
		if (width / 3 === rat.math.floor(width / 3))
			div = 3;
		this.blockSize = new rat.Vector(width / div, height / div);
		this.blockRows = 3; //	box
		if (height === width / div)	//	bar - just one row
		{
			this.blockRows = 1;
			this.blockSize.y = image.height;
		}
		
		this.setDirty(true);
	};
	
	//	explicitly use this imageref instead of loading above
	rat.ui.BubbleBox.prototype.useImageRef = function (imageRef)
	{
		this.imageRef = imageRef;
		var imageSize = this.imageRef.getFrameSize(0);
		this.updateWithNewImageSize(imageSize.w, imageSize.h);
		
		this.setDirty(true);
	};
	
	//	Update my image, in case it needs to animate or something.
	//	This behavior currently needs to be the same as Sprite's updateSelf,
	//	so I'm just going to call that function, for now.
	//	Later, this might argue for a refactor, or even making this class a subclass of sprite,
	//	but right now that seems like overkill.
	rat.ui.BubbleBox.prototype.updateSelf = function (dt)
	{
		rat.ui.Sprite.prototype.updateSelf.call(this, dt);
	};
	
	//	todo:  why is there no update function here to support animating my imageRef?
	//	if we add one, be sure to consider setting my dirty state.

	rat.ui.BubbleBox.prototype.drawSelf = function ()
	{
		var ctx = rat.graphics.getContext();
		if (this.imageRef === void 0)
			return;
		var image = this.imageRef.getImage();
		if (image === null)
			return;

		//	Deal with scaling...
		//	If context is scaled, we aren't going to look very good, on some platforms (Win8, maybe IE?)
		//	So...  undo all transforms, calculate raw screen pixel points, and draw in a raw identity-matrix context
		//	TODO:  Does not play nicely with rotation, currently.  We need to extract rotation out of matrix or something?
		//	Or keep track of collected scale and rotation separately in rat.graphics.transform api.  Yeah, probably...
		var scaleHack = false;
		var width = rat.math.floor(this.size.x);
		var height = rat.math.floor(this.size.y);
		if ((this.flags & rat.ui.Element.adjustForScaleFlag) &&
			(rat.graphics.mTransform.m[0][0] !== 1 || rat.graphics.mTransform.m[1][1] !== 1))
		{
			scaleHack = true;
			//var topLeft = rat.graphics.transformPoint(this.place.pos);
			//var botRight = rat.graphics.transformPoint({ x: this.place.pos.x + this.size.x, y: this.place.pos.y + this.size.y });
			var topLeft = rat.graphics.transformPoint({ x: 0, y: 0 });
			var botRight = rat.graphics.transformPoint({ x: this.size.x, y: this.size.y });

			rat.graphics.save();
			ctx.setTransform(1, 0, 0, 1, 0, 0);

			width = rat.math.floor(botRight.x - topLeft.x);
			height = rat.math.floor(botRight.y - topLeft.y);

			topLeft.x = rat.math.floor(topLeft.x);
			topLeft.y = rat.math.floor(topLeft.y);
			ctx.translate(topLeft.x, topLeft.y);

			//	special case for bubble bars (bubbles that only have one row)
			//	In this case, we are not actually tiling vertically, so we won't have artifacts.
			//	So, go ahead and scale vertically to get what we want.
			//	Will this look funny?
			if (this.blockRows === 1)
			{
				ctx.scale(1, height / this.size.y);
				height = this.size.y;
			}

			//	temp test
			//ctx.fillStyle = "#FFFFFF";
			//ctx.fillRect(topLeft.x, topLeft.y, 20, 20);
			//ctx.fillRect(botRight.x, botRight.y, 20, 20);

			/*
			var m11 = rat.graphics.mTransform.m[0][0];
			var m12 = rat.graphics.mTransform.m[1][0];
			var m21 = rat.graphics.mTransform.m[0][1];
			var m22 = rat.graphics.mTransform.m[1][1];
			var mdx = rat.math.floor(rat.graphics.mTransform.m[0][2]);
			var mdy = rat.math.floor(rat.graphics.mTransform.m[1][2]);
			ctx.setTransform(m11, m12, m21, m22, mdx, mdy);
			*/
		}

		var rows = height / this.blockSize.y;
		if (this.blockRows === 1)	//	support for bubble bars - just truncate to one row
			rows = 1;
		var rowsFloor = rat.math.floor(rows);
		var cols = width / this.blockSize.x;
		var colsFloor = rat.math.floor(cols);

		//console.log("rows, cols:  " + rows + ", " + cols);
		//console.log("blockSize: " + this.blockSize.x + ", " + this.blockSize.y);

		//	util to get pos and width of source tile, based on our position in the loop.
		//	useful for both x and y
		var tileWidth = 0;
		function getTileInfo(index, count, countFloor, pieceSize)
		{
			tileWidth = pieceSize;
			if (index === 0)	//	first tile - assume we're wide enough and just grab the whole thing
				return 0;
			else if (index >= count - 1)	//	last tile
			{
				//	special case...  we didn't have room for middle tiles, and our final tile is too wide, so crop it down, eating away from the left, leaving the right
				if (count > 1 && count < 2)
					tileWidth = (count - countFloor) * pieceSize;
				return pieceSize * 2 + (pieceSize - tileWidth);
			}
			else	//	middle tile - we crop the last middle tile to exactly fill space remaining before final tile
			{
				if (index >= countFloor - 1)
					tileWidth = (count - countFloor) * pieceSize;
				return pieceSize;
			}
		}

		var yPos = 0;
		var sourceX = 0, sourceY = 0, sourceWidth, sourceHeight;
		if (this.drawType === rat.ui.BubbleBox_TILE)
		{
			for(var y = 0; y < rows; y++)
			{
				sourceY = getTileInfo(y, rows, rowsFloor, this.blockSize.y);
				sourceHeight = tileWidth;

				var xPos = 0;
				for(var x = 0; x < cols; x++)
				{
					sourceX = getTileInfo(x, cols, colsFloor, this.blockSize.x);
					sourceWidth = tileWidth;

					//console.log("  draw " + x + "," + y);
					ctx.drawImage(image, sourceX, sourceY, sourceWidth, sourceHeight, xPos - this.center.x, yPos - this.center.y, sourceWidth, sourceHeight);
					//console.log("x draw " + x + "," + y);

					xPos += sourceWidth;
				}

				yPos += sourceHeight;
			}
		}
		else if (this.drawType === rat.ui.BubbleBox_STRETCH)
		{
			// nine total calls max instead of possibly hundreds

			// draw each of the nine elements once instead of tiling
			var farXPos = width - this.center.x;
			var farYPos = height - this.center.y;

			// top left corner y:0, x:0
			sourceY = getTileInfo(0, rows, rowsFloor, this.blockSize.y);
			sourceHeight = tileWidth;		// missed this the first time through because I didnt realize it was due to side effects from the function call
			sourceX = getTileInfo(0, cols, colsFloor, this.blockSize.x);
			sourceWidth = tileWidth;
			ctx.drawImage(image, sourceX, sourceY, sourceWidth, sourceHeight, -this.center.x, -this.center.y, sourceWidth, sourceHeight);

			// top right corner y:0, x:cols-1
			sourceY = getTileInfo(0, rows, rowsFloor, this.blockSize.y);
			sourceHeight = tileWidth;
			sourceX = getTileInfo(colsFloor, cols, colsFloor, this.blockSize.x);
			sourceWidth = tileWidth;
			ctx.drawImage(image, sourceX, sourceY, sourceWidth, sourceHeight, farXPos - sourceWidth, -this.center.y, sourceWidth, sourceHeight);

			// bottom left corner y:rows-1, x:0
			sourceY = getTileInfo(rowsFloor, rows, rowsFloor, this.blockSize.y);
			sourceHeight = tileWidth;
			sourceX = getTileInfo(0, cols, colsFloor, this.blockSize.x);
			sourceWidth = tileWidth;
			ctx.drawImage(image, sourceX, sourceY, sourceWidth, sourceHeight, -this.center.x, farYPos - sourceHeight, sourceWidth, sourceHeight);

			// bottom right corner y:rows-1, x:cols-1
			sourceY = getTileInfo(rowsFloor, rows, rowsFloor, this.blockSize.y);
			sourceHeight = tileWidth;
			sourceX = getTileInfo(colsFloor, cols, colsFloor, this.blockSize.x);
			sourceWidth = tileWidth;
			ctx.drawImage(image, sourceX, sourceY, sourceWidth, sourceHeight, farXPos - sourceWidth, farYPos - sourceHeight, sourceWidth, sourceHeight);

			if (cols > 2)
			{
				// top middle
				sourceY = getTileInfo(0, rows, rowsFloor, this.blockSize.y);
				sourceHeight = tileWidth;
				sourceX = getTileInfo(1, cols, colsFloor, this.blockSize.x);
				sourceWidth = tileWidth;
				ctx.drawImage(image, sourceX, sourceY, sourceWidth, sourceHeight, sourceWidth - this.center.x, -this.center.y, width - sourceWidth * 2, sourceHeight);

				// bottom middle
				sourceY = getTileInfo(rowsFloor, rows, rowsFloor, this.blockSize.y);
				sourceHeight = tileWidth;
				sourceX = getTileInfo(1, cols, colsFloor, this.blockSize.x);
				sourceWidth = tileWidth;
				ctx.drawImage(image, sourceX, sourceY, sourceWidth, sourceHeight, sourceWidth - this.center.x, farYPos - sourceHeight, width - sourceWidth * 2, sourceHeight);
			}

			if (rows > 2)
			{
				// left middle
				sourceY = getTileInfo(1, rows, rowsFloor, this.blockSize.y);
				sourceHeight = tileWidth;
				sourceX = getTileInfo(0, cols, colsFloor, this.blockSize.x);
				sourceWidth = tileWidth;
				ctx.drawImage(image, sourceX, sourceY, sourceWidth, sourceHeight, -this.center.x, sourceHeight - this.center.y, sourceWidth, height - sourceHeight * 2);

				// right middle
				sourceY = getTileInfo(1, rows, rowsFloor, this.blockSize.y);
				sourceHeight = tileWidth;
				sourceX = getTileInfo(colsFloor, cols, colsFloor, this.blockSize.x);
				sourceWidth = tileWidth;
				ctx.drawImage(image, sourceX, sourceY, sourceWidth, sourceHeight, farXPos - sourceWidth, sourceHeight - this.center.y, sourceWidth, height - sourceHeight * 2);
			}

			if (rows > 2 && cols > 2)
			{		// only draw if there is a middle area, only exists if rows and column sizes are greater than 2
				// middle middle
				sourceY = getTileInfo(1, rows, rowsFloor, this.blockSize.y);
				sourceHeight = tileWidth;
				sourceX = getTileInfo(1, cols, colsFloor, this.blockSize.x);
				sourceWidth = tileWidth;
				if (rows > 2 && cols > 2)
					ctx.drawImage(image, sourceX, sourceY, sourceWidth, sourceHeight, sourceWidth - this.center.x, sourceHeight - this.center.y, width - sourceWidth * 2, height - sourceHeight * 2);
			}
		}

		if (scaleHack)
			rat.graphics.restore();
	};

	rat.ui.BubbleBox_setDefaultDrawType = function (drawType)
	{
		if (drawType === rat.ui.BubbleBox_STRETCH)
			rat.ui.BubbleBox_defaultDrawType = rat.ui.BubbleBox_STRETCH;
		else
			rat.ui.BubbleBox_defaultDrawType = rat.ui.BubbleBox_TILE;
	};
} );
//--------------------------------------------------------------------------------------------------
//	sprite Element (subclass of ui Element)
//	renders with a loaded image

//	TODO:
//		Figure out how to autoscale sprites when setSize() is called, but factor in
//			images that load late
//			setting size before image loads
//			setting size after image loads
//			loading a new image after setting size...
//			all of the above for buttons which use images.  (spritebuttons)
//			see partially written code below. :(
//			would be very nice to figure out some solution to that "isn't loaded yet, can't get size" thing...
//			maybe require preloading, or sprite sheets, or something.
//	
//			The more I work with this auto-whatever-after-load flag business,
//			the more I think the policy should be: preload your dang images, or all bets are off.
//			And then we should just make it easier to preload images?
//
//			Also note that scaling the whole element just to fit the sprite seems incorrect.
//			We should only be scaling the sprite itself to fit our size.
//			For instance, scaling the whole element results in a scaled frame, which is annoying.
//			I'm going to try switching to an internal image scale here instead of element scale.
//
rat.modules.add( "rat.ui.r_ui_sprite",
[
	{name: "rat.ui.r_ui", processBefore: true },
	{name: "rat.utils.r_utils", processBefore: true },
	
	"rat.graphics.r_graphics",
	"rat.graphics.r_image",
	"rat.debug.r_console",
	"rat.ui.r_ui_data",
], 
function(rat)
{
	/**
	 * @constructor
	 * @extends rat.ui.Element
	 * @param {string|Array=} resource
	 * @param {?} extra1
	 * @param {?} extra2
	*/
	rat.ui.Sprite = function (resource, extra1, extra2)
	{
		//	is first arg our parent, or a resource?
		var parent = null;
		if (resource && resource.elementType)
		{
			parent = resource;
			resource = null;
		}
		rat.ui.Sprite.prototype.parentConstructor.call(this, parent); //	default init
		this.flags |= rat.ui.Element.autoSizeAfterLoadFlag;	//	by default, we autosize ourself to match image size after image load
		//	todo: But a manual call to setSize() ought to clear that flag, right?  e.g. user wants to set size explicitly...?
		//	maybe...  see autoScaleAfterLoadFlag

		this.imageScaleX = this.imageScaleY = 1;
		
		this.name = "<sprite>" + this.id;	//	+ resource
		this.loadImage(resource, extra1, extra2);	//	do actual image load, or at least set it up
		this.setTracksMouse(false);	//	no mouse tracking, highlight, tooltip, etc. including subelements.
	};
	rat.utils.inheritClassFrom(rat.ui.Sprite, rat.ui.Element);
	rat.ui.Sprite.prototype.elementType = 'sprite';
	
	//	utility to call after a sprite loads,
	//	to adjust size/scale values based on flags.
	rat.ui.Sprite.prototype.handleImageFlags = function(imageRef)
	{
		//	autoscale to size
		//	or autosize to content.  These two are mutually exclusive,
		//	and we prioritize autoScaleAfterLoadFlag since autoSize is a default and needs to be easy to override
		
		var self = this;
		if (self.flags & rat.ui.Element.autoScaleAfterLoadFlag)	//	scale image to match our size
		{
			self.scaleImageToSize();
		}
		else if (self.flags & rat.ui.Element.autoSizeAfterLoadFlag)	//	set our size to match image size
		{
			self.sizeToImage();	//	calls boundschanged
		}
		
		if (self.flags & rat.ui.Element.autoCenterAfterLoadFlag)
		{
			self.autoCenter(true);
		}

		var imageSize = self.imageRef.getFrameSize(0);
		self.setContentSize(imageSize.w, imageSize.h);	//	set my content size to match the content we just loaded

		self.setDirty(true);
	};

	//	Load this resource in as the new image.
	/**
	 * @param {string|Array=} resource
	 * @param {?} extra1
	 * @param {?} extra2
	*/
	rat.ui.Sprite.prototype.loadImage = function (resource, extra1, extra2)
	{
		this.resource = resource;	//	I don't think this is used.  TODO: remove this line.
		if (resource && resource !== void 0)
		{
			this.imageRef = rat.graphics.makeImage(resource, extra1, extra2);
			//	"makeImage" is a more flexible way of creating sprites - may return a different type of imageref as needed.
			var self = this;	//	set up reference to self for use in closure below
			this.imageRef.setOnLoad(function ()
			{
				self.handleImageFlags(this.imageRef);
				
				//	and if somebody set up an additional callback, call that now.
				if (self.onLoad)
					self.onLoad(self.onLoadArg);
			});
			
			//	for convenient debug purposes, set our internal element name to include the source used.
			if (typeof(resource) === 'string')
				this.name = "<sprite>" + resource + this.id;
		}
	};
	
	//	explicitly use this imageref instead of loading above
	rat.ui.Sprite.prototype.useImageRef = function (imageRef)
	{
		this.imageRef = imageRef;
		if (this.imageRef.isLoaded())
			this.handleImageFlags(this.imageRef);
		else {
			var self = this;
			this.imageRef.setOnLoad(function ()
			{
				self.handleImageFlags(this.imageRef);
			});
		}

		this.setDirty(true);
	};
	
	//	get back whatever imageref we're using.
	rat.ui.Sprite.prototype.getImageRef = function ()
	{
		return this.imageRef;
	};

	//	explicitly reset my element size to the size of my loaded image.
	//	assumes the image is done loading.
	rat.ui.Sprite.prototype.sizeToImage = function ()
	{
		if (this.imageRef)
		{
			//console.log("sizetoimage");
			var imageSize = this.imageRef.getFrameSize(0);
			this.setSize(imageSize.w, imageSize.h);	//	calls boundschanged
		}
	};
	//	explicitly reset my scale to my ui element size (with an optional extra scale to also apply)
	//	assumes the image is done loading.
	rat.ui.Sprite.prototype.scaleImageToSize = function (extraScaleX, extraScaleY)
	{
		if (!this.imageRef)
			return;
		
		extraScaleX = extraScaleX || 1;
		extraScaleY = extraScaleY || 1;
		
		var imageSize = this.imageRef.getFrameSize(0);
		//	track internal image scale instead of scaling element.
		//	This is so the image can be scaled independently of the ui element's scale value,
		//	So things like getBounds() aren't affected by how the sprite draws.
		//	I'm not sure why we do it this way.  What is the basic element scale property for, if not this?
		this.imageScaleX = this.size.x/imageSize.w * extraScaleX;
		this.imageScaleY = this.size.y/imageSize.h * extraScaleY;
		//this.setScale(, this.size.y/imageSize.h);
		
		this.setDirty(true);
	};
	
	//	directly set our separate scale factor
	rat.ui.Sprite.prototype.setImageScale = function (scaleX, scaleY)
	{
		this.imageScaleX = scaleX;
		this.imageScaleY = scaleY;
		
		this.setDirty(true);
	};

	//	auto center for sprites
	//	if sprite is not loaded, do autocenter after load.
	rat.ui.Sprite.prototype.autoCenter = function (doCenter)
	{
		if (doCenter === void 0)
			doCenter = true;
		
		//	Old code was doing nothing if the sprite happened to have no image ref (yet).
		//	so, later when new imageref is loaded in, the correct center is not set.
		//	So, current logic:  set afterload flag only if we HAVE an image and it's being loaded.
		//		otherwise, immediately calculate new center.
		if (doCenter && typeof(this.imageRef) !== "undefined" && !this.imageRef.isLoaded())
		{
			//console.log("delayed autocenter A");
			this.flags |= rat.ui.Element.autoCenterAfterLoadFlag;
		} else {
			rat.ui.Sprite.prototype.parentPrototype.autoCenter.call(this, doCenter);	//	inherited normal func
		}
	};

	//	turn on custom outline mode for sprites
	rat.ui.Sprite.prototype.setOutline = function (enabled, scale)
	{
		if (this.outline !== enabled || this.outlineScale !== scale)
			this.setDirty(true);
		
		this.outline = enabled;
		this.outlineScale = scale;
	};

	//	Update my image, in case it needs to animate or something.
	rat.ui.Sprite.prototype.updateSelf = function (dt)
	{
		if (this.imageRef)
		{
			//	update our image, in case it's animated.
			//	NOTE:  If you're using an animated image, it's probably not a good idea to use offscreen functionality,
			//	but just in case, we support it here with a dirty check.  If your image only sometimes animates, that's probably fine.
			var oldFrame = this.imageRef.getFrame();
			this.imageRef.update(dt);
			if (this.imageRef.getFrame() !== oldFrame)
				this.setDirty(true);
		}
	};
	
	//	Draw my sprite, tiled into this space.
	//	This function is useful for being called directly, from outside of the normal UI draw process.
	//	It acts handles positioning and rotation, but doesn't draw subelements.
	//	(Actually, this is a little weird - why does it do SOME of the standard draw, with copied and pasted code?
	//		Seems like this function should be rewritten, calling draw() instead...?)
	//	So, don't use it like it is for normal UI hierarchy drawing.  Just use the tiled flag for that.  See below.
	rat.ui.Sprite.prototype.drawTiled = function (w, h)
	{
		if (!this.isVisible())	//	don't draw me or sub stuff if I'm invisible
			return;
		
		rat.graphics.frameStats.totalElementsDrawn++;	//	for debugging, track total elements drawn per frame
		var ctx = rat.graphics.getContext();
		rat.graphics.save();

		rat.graphics.translate(this.place.pos.x, this.place.pos.y);
		if (this.place.rot.angle)
			rat.graphics.rotate(this.place.rot.angle);
		if (this.scale.x !== 1 || this.scale.y !== 1)
			rat.graphics.scale(this.scale.x, this.scale.y);
		ctx.globalAlpha = this.opacity;
		
		if (this.frameWidth > 0)
		{
			ctx.strokeStyle = this.frameColor.toString();
			ctx.lineWidth = this.frameWidth;
			ctx.strokeRect(-this.center.x - this.frameOutset, -this.center.y - this.frameOutset,
					this.size.x + 2 * this.frameOutset, this.size.y + 2 * this.frameOutset);
		}

		if (this.flags & rat.ui.Element.clipFlag)
		{
			ctx.beginPath();
			ctx.moveTo(0, 0);
			ctx.lineTo(this.size.x, 0);
			ctx.lineTo(this.size.x, this.size.y);
			ctx.lineTo(0, this.size.y);
			ctx.lineTo(0, 0);
			ctx.clip();
		}
		
		//	do the actual drawing
		this.drawMyImageTiled(w, h);
		
		rat.graphics.restore();
	};
	
	//	simple internal utility for drawing my image tiled.  Used by several other functions to do the actual tiled draw.
	//	tile the image as many times as needed to hit w/h
	rat.ui.Sprite.prototype.drawMyImageTiled = function (w, h)
	{
		var ctx = rat.graphics.getContext();
		var imageSize = this.imageRef.getSize();
		for (var x = 0; x < w; x += imageSize.w) {
			for (var y = 0; y < h; y += imageSize.h) {
				//	todo:  Do we need this translate/restore approach?  Why not just pass position args to draw()?
				rat.graphics.save();
				
				var width;
				var height;
				if (imageSize.w + x > w)
					width = w - x;
				else
					width = imageSize.w;
				if (imageSize.h + y > h)
					height = h - y;
				else
					height = imageSize.h;
				rat.graphics.translate(x, y);
				this.imageRef.draw(ctx, 0, 0, width, height, 0, 0, width, height);
				
				rat.graphics.restore();
			}
		}
	};

	//	Draw me
	rat.ui.Sprite.prototype.drawSelf = function ()
	{
		if ((this.flags & rat.ui.Element.drawTiledFlag) !== 0)
		{
			this.drawMyImageTiled(this.size.x, this.size.y);
			
		} else if (this.imageRef)
		{
			var ctx = rat.graphics.getContext();
			//	some custom code for faking outlines around objects by drawing them bigger in a funky mode.
			if (this.outline)
			{
				var frameSize = this.imageRef.getSize();
				ctx.globalCompositeOperation = 'destination-out';
				var ow = frameSize.w * this.outlineScale;
				var oh = frameSize.h * this.outlineScale;
				var dx = (ow - frameSize.w) / 2;
				var dy = (oh - frameSize.h) / 2;
				this.imageRef.draw(ctx, -(this.center.x + dx), -(this.center.y + dy), ow, oh);
				ctx.globalCompositeOperation = 'source-over';	//	back to normal
			}

			//	normal draw, factoring in scale.
			var imageSize = this.imageRef.getSize();
			var w = imageSize.w * this.imageScaleX;
			var h = imageSize.h * this.imageScaleY;

			this.imageRef.draw(ctx, -this.center.x, -this.center.y, w, h);
			
			/*
			image = this.imageRef.getImage();
			if (image != null)
				ctx.drawImage(image, -this.center.x, -this.center.y);
			else
			{
				//	not ready yet...
				//console.log("invalid sprite");
			}
			*/
		}
	};

	//	sprite bounds changed.
	//	adjust scale/size based on flags?
	rat.ui.Sprite.prototype.boundsChanged = function ()
	{
		//	if we should be scaling our content to match our size, and content is loaded, do that now.
		if ((this.flags & rat.ui.Element.autoScaleAfterLoadFlag) && (this.imageRef !== void 0))
		{
			this.scaleImageToSize();
		}
	
		rat.ui.Sprite.prototype.parentPrototype.boundsChanged.call(this);	//	inherited normal func
	};
	
	//	set this callback to happen after my sprite image is loaded.
	rat.ui.Sprite.prototype.setOnLoad = function (func, arg)
	{
		this.onLoad = func;
		this.onLoadArg = arg;
	};
	
	//--------------------------------------------------------------------------------------
	//	Setup from data
	//autoCenter: true,
	//outline: scale,
	//resource:""/[]
	
	rat.ui.Sprite.editProperties = [
	{ label: "image",
		props: [
			{propName:'resource', type:'resource'},
			{propName:'outline', type:'float'},
			//{propName:'autoCenter', type:'boolean'},
		],
	}
	];

	rat.ui.Sprite.setupFromData = function (pane, data, parentBounds)
	{
		rat.ui.data.callParentSetupFromData(rat.ui.Sprite, pane, data, parentBounds);

		if (data.outline)
			pane.setOutline(true, data.outline);
		if (data.onLoad)
			pane.setOnLoad(data.onLoad, pane);
		if (data.resource)
			pane.loadImage(data.resource);
		if (data.animSpeed && pane.imageRef)
			pane.imageRef.animSpeed = data.animSpeed;
		
		//	If a size was set in the data, then re-set it here.  This is because pane.loadImage may change it.

	};
});
//--------------------------------------------------------------------------------------------------
//
//	Rat cycle updating library. Registers function to call once per frame
//
//	Note that the order in which functions will be called in NOT guaranteed.
//

//------------ rat.cycleUpdate ----------------
rat.modules.add( "rat.utils.r_cycleupdate",
[], 
function(rat)
{
	/** CycleUpdate module */
	rat.cycleUpdate = {
		updaters: []/* Holds {func:<func>, withThis:<thisObj>}*/
	};
	var updaters = rat.cycleUpdate.updaters;
	var cUpdating = false;
	
	/**
	 * Register a function that will be ran once per frame
	 * @param {function(number)} updateFunc
	 */
	rat.cycleUpdate.addUpdater = function (updateFunc)
	{
		if( updateFunc )
			updaters.push(updateFunc);
	};

	/**
	 * Unregister the update function
	 * @param {function(number)} updateFunc that was previously registered
	 */
	rat.cycleUpdate.removeUpdater = function (updateFunc)
	{
		if(updateFunc)
		{
			//	Search
			var atIndex = updaters.indexOf(updateFunc);

			//	Found? Only clear it if we are updating, or remove it now
			if(atIndex !== -1)
			{
				if(!cUpdating)
					updaters.splice(atIndex, 1);
				else
					updaters[atIndex] = void 0;
			}
		}
	};

	/**
	 * Run all of the registered update functions
	 * @param {number} deltaTime
	 */
	rat.cycleUpdate.updateAll = function (deltaTime)
	{
		cUpdating = true;
		for(var index = updaters.length - 1; index >= 0; --index)
		{
			//	If we hit a non-existent updater, it was remove, so remove it now
			if(!updaters[index])
				updaters.splice(index, 1);
			else
				updaters[index](deltaTime);
		}
		cUpdating = false;
	};

	
} );
//--------------------------------------------------------------------------------------------------
//
// String localization module
//
// Handle pulling of a string based on locale
//
rat.modules.add( "rat.utils.r_string",
[
	"rat.debug.r_console",
	"rat.os.r_system",
	"rat.utils.r_utils",
], 
function(rat)
{
	var STATES = {
		init: "uninit",				//	the rat string objects initial state
		requested: "requested",		//	We are requesting files
		complete: "complete",		//	The files are all done (successfully)
		error: "error"				//	A file failed!
	};
	
	rat.string = {
		currentStatus: STATES.init,			// valid status should be STATES.<state>.  See above
		currentLanguage: "english",			//	The language to used based on the currently set locale
		currentLocale: "en-US",				// the locale we are in, eg en-US for us english, es-ES for spain spanish, ca-FR for French canadian, etc - defaults to US

		stringData: {},						// The string data as defined by our localization strings file 

		supportedLanguages: [],				// When we load in the string data we will populate this with supported languages in order

		callback: void 0,
	};
	rat.string.hasLanguage = {};

	rat.string.useXMLFile = !!window.DOMParser;

	/**  Fire the done callback
 */
	rat.string.fireDoneCallback = function ()
	{
		// not sure we should always call the callback even if it errors, but if it errors it is done...
		if (rat.string.callback)
		{
			rat.string.callback();
			rat.string.callback = void 0;
		}
	};

	// initialize, make checks for platform
	var stringFilesPending = 0;
	rat.string.init = function (filenames, callback)
	{
		if (rat.string.currentStatus !== STATES.init)
		{
			rat.console.log("rat.string.init called twice   This is NOT allowed");
			return;
		}

		rat.string.callback = callback;
		if (Array.isArray(filenames) === false)
		{
			if (filenames)
				filenames = [filenames];
			else
				filenames = [];
		}
		stringFilesPending = filenames.length;
		
		if (filenames.length > 0)
		{
			rat.string.currentStatus = STATES.requested;

			if (rat.system.has.xboxLE)
			{
				Maple.addEventListener(ORMMA_EVENT_RESPONSE, rat.string.processUrlCallback);
				Maple.addEventListener(ORMMA_EVENT_ERROR, rat.string.processUrlError);
			}

			for (var index = 0; index !== filenames.length; ++index)
			{
				var filename = filenames[index];

				//	Make sure that have have the correct extension (XML/JSON) based on our platform
				//	First, strip any extension that is there
				var extAtIndex = filename.lastIndexOf(".");
				if( extAtIndex !== -1 )
					filename = filename.slice(0, extAtIndex);
				//	And add the correct extension
				if (rat.string.useXMLFile)
					filename += ".xml";
				else
					filename += ".json";

				//rat.console.log( "Loading string file " + filename);
				if (rat.system.has.xboxLE)
					Maple.request(adParams._projectBase + filename, "proxy", "GET");
				else if (rat.string.useXMLFile)
					rat.utils.loadXML(filename, rat.string.loadData);		// loadData sets status to completed when its finished
				else
					rat.utils.loadJSON(filename, rat.string.loadData);		// loadData sets status to completed when its finished
			}
		}
		else
		{
			rat.string.currentStatus = STATES.complete;
			rat.string.fireDoneCallback();
		}
	};

	// got the XML data, load it up
	rat.string.processUrlCallback = function (url, data)
	{
		if (rat.string.currentStatus !== STATES.requested)
			return;

		rat.string.loadData(data);

		if (stringFilesPending <= 0)
		{
			Maple.removeEventListener(ORMMA_EVENT_RESPONSE, rat.string.processUrlCallback);
			Maple.removeEventListener(ORMMA_EVENT_ERROR, rat.string.processUrlError);
		}
	};

	// failed the callback, probably default to english?
	rat.string.processUrlError = function (message, action)
	{
		if (rat.string.currentStatus !== STATES.requested)
			return;

		stringFilesPending = 0;
		rat.string.currentStatus = STATES.error;
		Maple.removeEventListener(ORMMA_EVENT_RESPONSE, rat.string.processUrlCallback);
		Maple.removeEventListener(ORMMA_EVENT_ERROR, rat.string.processUrlError);

		//	Should we fire this if we fail?
		rat.string.fireDoneCallback();
	};

	// have we done all we can and are ready to start the game up?
	rat.string.isReady = function ()
	{
		// if we error'ed out we will use the default language
		if (rat.string.currentStatus === STATES.complete || rat.string.currentStatus === STATES.error)
			return true;
		return false;
	};

	rat.string.selectLanguage = function ()
	{
		var locale = ["en-US"];
		if (rat.system.has.winJS)
			locale = window.Windows.System.UserProfile.GlobalizationPreferences.languages;

		if (locale && locale[0])
			locale = locale[0];
		//locale = "es-ES"
		rat.string.setLocale(locale);
		rat.console.log("Using locale :" + rat.string.currentLocale + " with language " + rat.string.currentLanguage);
	};

	rat.string.setLocale = function (locale)
	{
		rat.string.currentLocale = locale;
		rat.string.currentLanguage = rat.string.convertLocaleToLanguage(rat.string.currentLocale);
	};

	rat.string.getLocale = function ()
	{
		return rat.string.currentLocale;
	};

	rat.string.getLanguage = function ()
	{
		return rat.string.currentLanguage;
	};

	rat.string.getString = function (stringName)
	{
		var language = rat.string.currentLanguage;

		if (!rat.string.stringData[language])
			language = rat.string.supportedLanguages[0];		// language not supported, use default

		var stringList = rat.string.stringData[language];

		if (stringList && stringList[stringName])
			return stringList[stringName].replace("\\n", "\n");

		return rat.string.getLanguageCode(language) + "_" + stringName;
	};

	//	Get a string, replacing tokens
	//	Usage examples.
	//	STR_CODE is "This is a {0} string" and STR_VAL is "Longest"
	//	getStringReplaceTokens("STR_CODE", "Long");	-> "This is a Long string"
	//	getStringReplaceTokens("STR_CODE", ["Longer"]);	-> "This is a Longer string"
	//	getStringReplaceTokens("STR_CODE", [{value: "STR_VAL", localize:true]);	-> "This is a Longer string"
	//	STR_CODE is "This is a {0} string {1}"
	//	getStringReplaceTokens("STR_CODE", ["Longer", "Buddy"]);	-> "This is a Longer string Buddy"
	//	getStringReplaceTokens("STR_CODE", [{key:"{0}", value:"Longer"}, "Buddy"]);		->	"This is a Longer string Buddy"
	//	STR_CODE is "This is a {CUSTOM} string"
	//	getStringReplaceTokens("STR_CODE", {key:"{CUSTOM}", value:"REALLY CUSTOM"});	->	"This is a REALLY CUSTOM string"
	rat.string.getStringReplaceTokens = function (stringCode, tokensArray)
	{
		tokensArray = tokensArray || [];
		if (Array.isArray(tokensArray) === false)
			tokensArray = [tokensArray];

		var string = rat.string.getString(stringCode);

		var token;
		var val;
		for (var index = 0; index < tokensArray.length; ++index)
		{
			token = tokensArray[index];
			if( token.value !== void 0 )
				val = token.value;
			else
				val = token;
			if (token.localize)
				val = rat.string.getString(val);
			if (token.key !== void 0)
				string = string.replace( token.key, val );
			else
				string = string.replace("{" + index + "}", val);
		}

		return string;
	};

	// Load all our localization information from file
	// not the most robust system and has a number of assumptions like the first column being the string keys, and
	//	the first row being the languages. It also requires the key column to have a language heading (I used "KEY")
	//	We should switch this system to use a JS file setup and make an excel exporter or similar tool accordingly
	//
	rat.string.loadData = function (in_Data)
	{
		//	ABORT if we are not loading.
		if (rat.string.currentStatus !== STATES.requested )
			return;

		// rat.console.log("load data from file: ");
		// rat.console.log("" + in_Data.substring(50, 100));

		if (rat.string.useXMLFile)
		{
			// rat.console.log("using xml parser");
			var parser = new DOMParser();
			var xmlData = parser.parseFromString(in_Data, "text/xml");
			var rows = xmlData.getElementsByTagName("Row");
			var lang;
			var j;
			for (var i = 0; i < rows.length; ++i)
			{
				var data = rows[i].getElementsByTagName("Cell");

				if (i === 0)
				{
					// language setup
					for (j = 0; j < data.length; ++j)
					{
						lang = data[j].textContent.toLowerCase();
						if (!rat.string.hasLanguage[lang])
						{
							rat.string.supportedLanguages.push(lang);
							rat.string.stringData[lang] = {};
							rat.string.hasLanguage[lang] = true;
						}
					}
				}
				else
				{
					// strings data
					for (j = 0; j < data.length; ++j)
					{
						lang = rat.string.supportedLanguages[j];
						var langData = rat.string.stringData[lang];

						langData[data[0].textContent] = data[j].textContent;
					}
				}
			}
		}
		else
		{
			//rat.console.log("not using xml parser:");
			var workbook = in_Data ? in_Data.Workbook : void 0;
			var worksheet = workbook ? workbook.Worksheet : workbook;
			var table = worksheet ? (worksheet.Table || worksheet[0].Table) : worksheet;
			var rowsData = table ? table.Row : table;
			if (!rowsData)
			{
				rat.console.log("FAILED to process string JSON file.  Bad format");
				return;
			}

			var language;
			var k;
			var col;
			for (var l = 0; l < rowsData.length; ++l)
			{
				var rowData = rowsData[l].Cell;

				if (l === 0)
				{
					// language setup
					for (k = 0; k < rowData.length; ++k)
					{
						col = rowData[k].Data || rowData[k]["ss:Data"];
						language = col["#text"].toLowerCase();
						if (!rat.string.hasLanguage[language])
						{
							// console.log("language " + language);
							rat.string.supportedLanguages.push(language);
							rat.string.stringData[language] = {};
							rat.string.hasLanguage = true;
						}
					}
				}
				else
				{
					// strings data
					if (!rowData.length || rowData.length === 1 ||
						(!rowData[0].Data && !rowData[0]["ss:Data"]) ||
						(!rowData[1].Data && !rowData[1]["ss:Data"]))
						continue;

					var firstCol = (rowData[0].Data || rowData[0]["ss:Data"]);

					for (k = 0; k < rowData.length; ++k)
					{
						col = rowData[k].Data || rowData[k]["ss:Data"];
						language = rat.string.supportedLanguages[k];
						var languageData = rat.string.stringData[language];

						// console.log("language: " + language + "   obj: " + languageData);
						// console.log("stringy blah (" + k + " of ");
						// console.log("    " + rowData.length);
						// console.log("key " + rowData[0].Data["#text"]);

						// console.log("data " + rowData[k].Data);
						if (col)
						{
							// console.log("string " + rowData[k].Data["#text"]);

							languageData[firstCol["#text"]] = col["#text"];
						}
					}
				}
			}

			//rat.console.log("Parsed");

		}

		--stringFilesPending;
		if (stringFilesPending <= 0)
		{
			rat.string.currentStatus = STATES.complete;
			rat.string.fireDoneCallback();
		}
	};

	rat.string.getLanguageCode = function (language)
	{
		switch (language)
		{
			case "english":					return "EN";
			case "french":					return "FR";
			case "italian":					return "IT";
			case "german":					return "DE";
			case "spanish":					return "ES";
			case "portuguese":				return "PT";
			case "japanese":				return "JA";
			case "korean":					return "KO";
			case "simplified chinese":		return "CS";
			case "traditional chinese":		return "CT";
			default: return "??";
		}
	};

	rat.string.convertLocaleToLanguage = function (locale)
	{
		//var language = "default";

		switch (locale.substring(0, 2))
		{
			case "en":
				return "english";
			case "fr":
				return "french";
			case "it":
				return "italian";
			case "de":
				return "german";
			case "es":
				return "spanish";
			case "pt":
				return "portuguese";
			case "ja":
				return "japanese";
			case "ko":
				return "korean";
			case "zh":
				if (locale === "zh-CN" || locale === "zh-SG" || locale === "zh-Hans")		// china, singapore
					return "simplified chinese";
				else											// taiwan, hong kong, macau
					return "traditional chinese";
				break;
			default:
				return "english";
		}
		//	default case above will get hit.  commenting this out to remove compile warning.
		//return language;
	};

	rat.strings = rat.string;
} );
//--------------------------------------------------------------------------------------------------
//
//	Animators for ui elements
//		Supporting simple property changes over time like position, rotation, fade, etc.
//		Can be used to animate other stuff (non ui objects), too, as long as function names match up
//
//	Usage:
//		* create one of these, attaching to a UI element.
//			the animator will get put in a global animators list that's updated every frame.
//
//		* for timed animation from a to b, use setTimer and setStartEnd
//		* for continuous increasing/decreasing animation, use setSpeed instead.
//
rat.modules.add( "rat.ui.r_ui_animator",
[
	{ name: "rat.ui.r_ui", processBefore: true },
	
	"rat.debug.r_console",
	"rat.math.r_vector",
	"rat.utils.r_utils",
], 
function(rat)
{
	rat.ui.animators = [];

	/**
	 * @constructor
	*/
	rat.ui.Animator = function (type, targetElement, id, relative)
	{
		//	third argument might actually be "relative" flag
		if (typeof (id) === "boolean" && arguments.length === 3) {
			relative = id;
			id = void 0;
		}
		
		this.id = id; // Possible custom ID
		this.type = type;
		this.relative = !!relative;
		this.target = targetElement;
		this.startValue = new rat.Vector(0, 0); //	initial value for animation, e.g. move, scale
		this.endValue = new rat.Vector(0, 0); //	destination for animation, e.g. move, scale

		this.startTime = -1;
		this.time = -1;			//	default to running infinitely
		this.delay = 0;
		//this.interp = 0;
		//	default to ease in/out, since usually that's what we'll want, I proclaim.
		//	if this is not desired, call clearInterpFilter.
		this.interpFilter = rat.ui.Animator.filterEaseInOut;

		this.flags = 0;
		this.setAutoDie(true);				//	this is almost always desired - kill animator (stop animating) when you're done animating.

		this.speed = 1; 					//	units per second - used when there's no timer (continuous)

		this.continuousVal = 0;				//	Continuous animation value.  Used to make repeating back-and-forth/in-out effect.
		this.continuousSign = 1;			//	Continuous animation "direction". Used to make repeating back-and-forth/in-out effect.
		
		this.delayCallback = null;
		this.doneCallback = null;

		if (!this.target)
		{
			rat.console.log("No target input for animator! Bad juju! Not adding it to the animator list");
			return;
		}

		this.targetStartState = {
			x: this.target.place.pos.x,
			y: this.target.place.pos.y,
			cx: this.target.contentOffset.x,
			cy: this.target.contentOffset.y,
			sx: this.target.scale.x,
			sy: this.target.scale.y,
			w: this.target.size.x,
			h: this.target.size.y,
			r: this.target.place.rot.angle,
			opacity: this.target.opacity
		};
		
		if (rat.ui.Animator.list[type])
			this.custom = rat.ui.Animator.list[type];
		if (this.custom && this.custom.init)
			this.custom.init(this);

		rat.ui.animators.push(this);
		//console.log("animator");
	};

	//	some constants for common animators.
	//	animation types:
	rat.ui.Animator.mover = 'mover'; //	move (use element place.pos)
	rat.ui.Animator.rotator = 'rotator'; //	rotate (use element place.rot)
	rat.ui.Animator.rotater = rat.ui.Animator.rotator;	//	alternate (more consistent) name
	rat.ui.Animator.scaler = 'scaler'; //	scale (use element scale)
	rat.ui.Animator.resizer = 'resizer'; //	resize (use element size)
	rat.ui.Animator.fader = 'fader'; //	fade (use opacity)
	rat.ui.Animator.scroller = 'scroller'; //	scroll  (use element contentoffset)
	//	todo: support rotate from center, or a new centerRotator?  or is that what centering code above was supposed to support? *sigh*
	//	todo: it'd be cool to have an animator that applies a rat ui flag (remembering the old flag setting) for a set time.  E.g. fake pressed for 1 second.
	
	//	a more generic system for registering animators.
	//	TODO: move most/all of the above into this system for more flexbility later
	rat.ui.Animator.list = {};
	
	//---- floater - our first "custom" type, as a test.
	//	This is a pretty dumb test, since it can be done with just a mover and a sin-based interp filter.
	rat.ui.Animator.floater = 'floater';
	rat.ui.Animator.list['floater'] = {
		propPath : "place.pos",
		init : function(animator) {
			//	our floatiness is already a filter, so don't *default* to ease in and out,
			//	but of course that can be reset if needed
			animator.interpFilter = rat.ui.Animator.filterLinear;
		},
		update : function(animator, interp, dt) {
			//	"float" here means use a sine-wave to interp between values.
			//	We assume the user wants to float around their original position,
			//	so we consider "endvalue" to be a sort of magnitude - we'll float that high and that low (can be negative)
			//interp = (Math.sin(interp * Math.PI * 2) + 1)/2;	//	speed already factored in?
			interp = Math.sin(interp * Math.PI * 2);	//	speed already factored in?
			var xVal = rat.utils.interpolate(animator.startValue.x, animator.endValue.x, interp);
			var yVal = rat.utils.interpolate(animator.startValue.y, animator.endValue.y, interp);

			if (animator.relative)
			{
				xVal += animator.targetStartState.x;
				yVal += animator.targetStartState.y;
			}

			animator.target.place.pos.x = xVal;
			animator.target.place.pos.y = yVal;

			//	todo: for ease of implementation, set flags for this part?
			//	this changes look of parent.  see element setScale
			if (animator.target.parent)
				animator.target.parent.setDirty(true);
		},
		resetTargetState : function(animator) {
			var startState = animator.targetStartState;
			animator.target.setPos(startState.x, startState.y);
		},
	};

	rat.ui.Animator.autoDieFlag = 0x01; //	kill this animator as soon as its timer runs out
	rat.ui.Animator.autoRemoveTargetFlag = 0x02; //	when this animator is done, remove target from parent

	//	animator functions
	rat.ui.Animator.prototype.setAutoDie = function (sense)
	{
		if (sense || sense === void 0)
			this.flags |= rat.ui.Animator.autoDieFlag;
		else
			this.flags &= ~rat.ui.Animator.autoDieFlag;
	};

	rat.ui.Animator.prototype.setAutoRemoveTarget = function (sense)
	{
		if (sense || sense === void 0)
			this.flags |= rat.ui.Animator.autoRemoveTargetFlag;
		else
			this.flags &= ~rat.ui.Animator.autoRemoveTargetFlag;
	};

	rat.ui.Animator.prototype.setDoneCallback = function (f)	//	call after done
	{
		this.doneCallback = f;
	};

	rat.ui.Animator.prototype.setDelayDoneCallback = function (f)	//	call after delay
	{
		this.delayCallback = f;
	};

	rat.ui.Animator.prototype.setTimer = function (time)
	{
		this.startTime = time;
		this.time = time;
	};
	rat.ui.Animator.prototype.setInfiniteTimer = function () {this.setTimer(-1);};

	rat.ui.Animator.prototype.setDelay = function (time)
	{
		this.delay = time;
	};

	rat.ui.Animator.prototype.setSpeed = function (speed)
	{
		this.speed = speed;
	};

	//	set start and end interpolation values in scalar form, for things like fade
	rat.ui.Animator.prototype.setStartEnd = function (startVal, endVal)
	{
		this.startValue.x = startVal;
		this.startValue.y = startVal;	//	copy to y as well, useful for things like uniform scale
		this.endValue.x = endVal;
		this.endValue.y = endVal;

		this.update(0);	//	start out at the start value now
	};

	//	set start and end interpolation values in vector form (for things like move)
	rat.ui.Animator.prototype.setStartEndVectors = function (startVal, endVal)
	{
		//	make this a little easier to call - if no startval is specified, try to figure out current value,
		//	and use that as start value.
		if (!startVal)
		{
			var curVal = this.getCurrentValueForType();
			this.startValue.x = curVal.x;
			this.startValue.y = curVal.y;
		} else {
			this.startValue.x = startVal.x;
			this.startValue.y = startVal.y;
		}
		this.endValue.x = endVal.x;
		this.endValue.y = endVal.y;

		this.update(0);	//	start out at the start value now
	};
	
	//
	//	Filter functions.
	//
	//	You can think of these as speed modifiers.
	//	"ease in", for instance, means start out at a speed of 0, and ease in to full speed.
	//	Each filter function takes input from 0-1 and returns output from 0-1,
	//	so they're easy to mix in to existing logic.
	//	There's nothing specific to the rat.ui.Animator module about these functions, just
	//	that they're convenient here.  You can use them in other modules,
	//	or move them some day to another place.
	//	Todo: Another module would be a good idea.
	
	//	For similar stuff, see:
	//	http://robertpenner.com/easing/
	//	http://easings.net/
	//	https://github.com/danro/jquery-easing/blob/master/jquery.easing.js
	//	http://gizma.com/easing/
	//	these robert penner functions take:
	//	cur interp, start value, change in value, total interp.
	//	or... 
	//	t = f
	//	b = 0
	//	c = 1
	//	d = 1
	//	So, you can take any penner function, plug in those values, and simplify.
	
	//	Don't change
	rat.ui.Animator.filterNone = function (f) {
		return 0;
	};

	//	Linear
	rat.ui.Animator.filterLinear = function (f) {
		return f;
	};

	//	Ease in and ease out.
	rat.ui.Animator.filterEaseInOut = function (f)
	{
		//return 3 * (f * f) - 2 * (f * f * f);
		return (f * f) * (3 - 2 * f);
	};
	//s(x) = sin(x*PI/2)^2 is slightly smoother?
	//s(x) = x - sin(x*2*PI) / (2*PI) is noticeably smoother?
	//see also http://gizma.com/easing/

	//	ease in, but then full speed to end
	rat.ui.Animator.filterEaseIn = function (f)
	{
		return (f * f);
	};
	
	//	full speed at start, then ease out speed at end.
	rat.ui.Animator.filterEaseOut = function (f)
	{
		return f * (2 - f);
	};
	
	//	ease in, ease out at destination halfway through time, and then ease back to start!
	//	this is nice for things like briefly scaling an object up and down with just one scaler animator.
	rat.ui.Animator.filterEaseThereAndBack = function (f)
	{
		if (f < 0.5)
			return rat.ui.Animator.filterEaseInOut(f*2);
		else
			return 1 - rat.ui.Animator.filterEaseInOut((f-0.5)*2);
	};
	
	//	these would be nice.  :)
	rat.ui.Animator.filterEaseInElastic = function (f)
	{
		return f;
	},
	
	rat.ui.Animator.filterEaseOutElastic = function(f)
	{
		if (f <= 0)
			return 0;
		if (f >= 1)
			return 1;
		
		//	how many times we bounce, basically. (how much we cut down on sin() frequency below)
		//	0.5 = very few bounces, 0.2 = lots of bounces.  0.3 was original.
		var p = 0.4;	//	0.3
		
		var s = p/(2*Math.PI) * 1.5707;//Math.asin (1);
		return Math.pow(2,-10*f) * Math.sin( (f-s)*(2*Math.PI)/p ) + 1;
	},
	
	rat.ui.Animator.prototype.setInterpFilter = function(filterFunc)
	{
		this.interpFilter = filterFunc;
	};
	rat.ui.Animator.prototype.clearInterpFilter = function()
	{
		this.setInterpFilter(null);
	};
	
	//	Update this animator
	rat.ui.Animator.prototype.update = function (dt)
	{
		if (!this.target)
			return false;

		if (this.delay > 0)
		{
			this.delay -= dt;
			if (this.delay > 0)
				return false;
			/** @todo	subtract leftover from timer as well? (for accuracy)
 */
			
			if (this.delayCallback)
				this.delayCallback(this);
		}

		var done = false;

		var interp = null;

		//	first figure out how much to change animation value, based on timer and timer type
		if (this.time < 0)	//	continuous mode
		{
			if (this.type === rat.ui.Animator.rotator)
				// For rotations, just do a continuous rotation, using this.speed as radians per second
				this.target.place.rot.angle += this.speed * dt;
			else if (this.custom)	//	for custom animators, loop 0-1 and repeat, and let them figure out the rest, or use continuousVal here...
			{
				this.continuousVal += this.speed * dt;
				interp = this.continuousVal % 1;
			}
			else
			{
				// For other types, calculate an interpolation value, and let the code below handle the rest.
				// Set up a "back-and-forth" type animation from 0 to 1 and back.
				// this.continuousVal is the current value in the 0 to 1 range.
				// this.continuousSign controls whether it's increasing or decreasing.
				//	This is a linear changing value, but of course a filter is still applied below for easing.
				this.continuousVal += this.continuousSign * this.speed * dt;
				if (this.continuousVal > 1)
				{
					this.continuousVal = 1;
					this.continuousSign = -this.continuousSign;
				}
				else if (this.continuousVal < 0)
				{
					this.continuousVal = 0;
					this.continuousSign = -this.continuousSign;
				}

				interp = this.continuousVal;
			}

		} else
		{	//	timer mode
			this.time -= dt;
			if (this.time < 0)
			{
				this.time = 0;
				done = true;
			}

			// Calculate interpolation value
			if (this.startTime <= 0)	//	somehow we were asked to take 0 seconds to animate...
				interp = 1;
			else
				interp = 1 - (this.time / this.startTime);

		}

		// Use interpolation value to set appropriate target values.
		if (interp !== null)
		{
			if (this.interpFilter !== void 0)
				interp = this.interpFilter(interp);
			
			//	custom type in list above?
			if (this.custom && this.custom.update)
			{
				this.custom.update(this, interp, dt);
			
			//	standard built-in types
			} else {
				var xVal = rat.utils.interpolate(this.startValue.x, this.endValue.x, interp);
				var yVal = rat.utils.interpolate(this.startValue.y, this.endValue.y, interp);

				//	then set target element's values based on animator type
				if (this.type === rat.ui.Animator.mover)
				{
					if (this.relative) {
						xVal += this.targetStartState.x;
						yVal += this.targetStartState.y;
					}

					this.target.place.pos.x = xVal;
					this.target.place.pos.y = yVal;
					
					//	this changes look of parent.  see element setScale
					if (this.target.parent)
						this.target.parent.setDirty(true);
				}
				else if (this.type === rat.ui.Animator.rotator)
				{
					if (this.relative)
						xVal += this.targetStartState.r;

					this.target.place.rot.angle = xVal;
					//	this changes look of parent.  see element setScale
					if (this.target.parent)
						this.target.parent.setDirty(true);
				}
				else if (this.type === rat.ui.Animator.scaler)
				{
					if (this.relative) {
						xVal += this.targetStartState.sx;
						yVal += this.targetStartState.sy;
					}
					this.target.setScale(xVal, yVal);
				}
				else if (this.type === rat.ui.Animator.resizer)
				{
					if (this.relative) {
						xVal += this.targetStartState.w;
						yVal += this.targetStartState.h;
					}
					this.target.setSize(xVal, yVal);
				}
				else if (this.type === rat.ui.Animator.fader)
				{
					if (this.relative)
						xVal += this.targetStartState.opacity;
					this.target.setOpacityRecursive(xVal);
				}
				else if (this.type === rat.ui.Animator.scroller)
				{
					if (this.relative) {
						xVal += this.targetStartState.cx;
						yVal += this.targetStartState.cy;
					}
					this.target.contentOffset.x = xVal;
					this.target.contentOffset.y = yVal;
					//	clamp?  Let's assume they know what they're doing...
					//	set dirty?  see element scroll function
					//	todo: see if there was actually a change
					if (this.target.viewChanged)
						this.target.viewChanged();
					
				}
			}
		}

		if (done && this.doneCallback)
		{
			//	warning - if you have a doneCallback and you don't autodie this animator, it'll get called over and over?
			var self = this;
			this.doneCallback(this.target, self);
		}

		if (done && (this.flags & rat.ui.Animator.autoRemoveTargetFlag))
		{
			this.target.removeFromParent();
		}

		if (done && (this.flags & rat.ui.Animator.autoDieFlag))
		{
			return true;
		}

		return false;
	};
	
	//	utility: given our type, get whatever our target's current value is.
	rat.ui.Animator.prototype.getCurrentValueForType = function()
	{
		if (this.type === rat.ui.Animator.mover)
			return this.target.place.pos;
		else if (this.type === rat.ui.Animator.rotator)
			return this.target.place.rot.angle;
		else if (this.type === rat.ui.Animator.scaler)
			return this.target.scale;
		else if (this.type === rat.ui.Animator.resizer)
			return this.target.size;
		else if (this.type === rat.ui.Animator.fader)
			return this.target.opacity;
		else if (this.type === rat.ui.Animator.scroller)
			return this.target.contentOffset;
		
		//	custom type?
		else if (this.custom && this.custom.propPath)
		{
			return rat.utils.get(this.target, this.custom.propPath);
		}
		
		return null;
	};

	rat.ui.Animator.prototype.die = function (dt)
	{
		this.target = null;	// remove target so I die next update
	};

	rat.ui.Animator.prototype.getElapsed = function (dt)
	{
		return this.startTime - this.time;
	};
	
	//	Finish an animation
	rat.ui.Animator.prototype.finish = function () {
		if (this.time < 0) {
			//rat.console.log("WARNING! Attempting to finish endless animator");
			return;	
		}
		else
		{
			this.delay = 0;
			this.update(this.time + 0.0001);
		}
	};

	//	Reset the targets properties to what they were BEFORE this animator changed them
	rat.ui.Animator.prototype.resetTargetState = function () {
		//	Only reset the things i change
		var startState = this.targetStartState;
		switch (this.type) {
			case rat.ui.Animator.mover:
				this.target.setPos(startState.x, startState.y);
				break;
			case rat.ui.Animator.rotator:
				this.target.setRotation(startState.r);
				break;
			case rat.ui.Animator.scaler:
				this.target.setScale(startState.sx, startState.sy);
				break;
			case rat.ui.Animator.resizer:
				this.target.setSize(startState.w, startState.h);
				break;
			case rat.ui.Animator.fader:
				this.target.setOpacity(startState.opacity);
				break;
			case rat.ui.Animator.scroller:
				this.target.setContentOffset(startState.cx, startState.cy);
				break;
			default:
				//	custom type?
				if (this.custom && this.custom.resetTargetState)
					this.custom.resetTargetState(this);
				break;
		}
	};
	
	var updatingAnimators = 0;
	rat.ui.updateAnimators = function (dt)
	{
		++updatingAnimators;
		for (var i = rat.ui.animators.length - 1; i >= 0; i--)
		{
			var kill = rat.ui.animators[i].update(dt);
			if (kill)
			{
				//rat.console.log("killed");
				rat.ui.animators.splice(i, 1);
			}
		}
		--updatingAnimators;
	};

	//	Reset Start state for all running animators on an element
	rat.ui.resetStateStateForAllAnimatorsForElement = function (element, animatorType) {
		//	todo refactor with function below
		for (var i = rat.ui.animators.length - 1; i >= 0; i--) {
			var anim = rat.ui.animators[i];
			if (!anim.target) {
				rat.console.log("JS FAILURE: animator is missing target! check that on construction all objects have a target!\n");
				rat.ui.animators.splice(i, 1);			// there is no target and thus should be no animator, purge it with fire!
				continue;
			}

			//	we check for equality by comparing objects here, instead of ID, since duplicate IDs might exist.
			//	If this is a problem, I recommend we have a new uniqueID property for each ui element, and compare that.  I think I've wanted that before for other things anyway...
			if (anim.target === element && (animatorType === void 0 || anim.type === animatorType)) {
				anim.resetTargetState();
			}
		}
	};

	//	Finish all running animators on an element
	rat.ui.finishAnimatorsForElement = function (element, animatorType, kill) {
		//	todo refactor with function below
		for (var i = rat.ui.animators.length - 1; i >= 0; i--) {
			var anim = rat.ui.animators[i];
			if (!anim.target) {
				rat.console.log("JS FAILURE: animator is missing target! check that on construction all objects have a target!\n");
				rat.ui.animators.splice(i, 1);			// there is no target and thus should be no animator, purge it with fire!
				continue;
			}

			//	we check for equality by comparing objects here, instead of ID, since duplicate IDs might exist.
			//	If this is a problem, I recommend we have a new uniqueID property for each ui element, and compare that.  I think I've wanted that before for other things anyway...
			if ( anim.target === element && (animatorType === void 0 || anim.type === animatorType) ) {
				anim.finish();

				if( kill && !updatingAnimators )
					rat.ui.animators.splice(i, 1);
			}
		}
	};

	//	kill any animators (with optional animator type check) attached to this element
	//	return number killed.
	rat.ui.killAnimatorsForElement = function (element, animatorType)
	{
		var killCount = 0;
		
		//	todo refactor with function below
		for (var i = rat.ui.animators.length - 1; i >= 0; i--)
		{
			var anim = rat.ui.animators[i];
			if (!anim.target)
			{
				// this really needs to be an assert
				rat.console.log("JS FAILURE: animator is missing target! check that on construction all objects have a target!\n");
				rat.ui.animators.splice(i, 1);			// there is no target and thus should be no animator, purge it with fire!
				continue;
			}

			//	we check for equality by comparing objects here, instead of ID, since duplicate IDs might exist.
			//	If this is a problem, I recommend we have a new uniqueID property for each ui element, and compare that.  I think I've wanted that before for other things anyway...
			if (
				anim.target === element &&
					(animatorType === void 0 || anim.type === animatorType)
			)
			{
				//rat.console.log("killed for " + element.id);
				rat.ui.animators.splice(i, 1);
				killCount++;
			}
		}
		
		return killCount;
	};
	
	//	get a list of animators for this element, possibly filtered to an ID
	rat.ui.getAnimatorsForElement = function (element, animatorType, id)
	{
		var list = [];
		for (var i = rat.ui.animators.length - 1; i >= 0; i--)
		{
			var anim = rat.ui.animators[i];
			if (anim.target === element
				&& (animatorType === void 0 || anim.type === animatorType)
				&& (id === void 0 || anim.id === id))
			{
				list.push(anim);
			}
		}
		return list;
	};

	//
	//	kill any registered animators
	//	(useful for cleanly getting rid of continuous ones...)
	rat.ui.killAnimators = function ()
	{
		//console.log("killing animators.  There are " + rat.ui.animators.length);
		rat.ui.animators = [];
	};

	//	lots to do...
	/*

	add to log.txt

	finish bubble box
	bubble bar support inside bubble box
	bubble button!
	various button details (highlights, etc.)

	ooh... cheats!  we should have a cheat dialog system somehow...
	frames for debugging
	more element subclasses
	sprite
	make them work
	sizes/centering for all
	shape graphic - work for circles, squares, paths

	add panes, UI, see notes on paper
	design better on paper - highlights - how do they work?  active, target, etc., like wraith?
	buttons, with frames, that highlight.
	bubble boxes
	buttons
	bubble buttons

	eventually, support tinting by drawing element to offscreen canvas with fancy canvas operations?
	It'll be slow, but for UI maybe it's OK.
	Won't work well for particles, because of speed concerns.  :(

	add more animator support, clean up and fix


	*/

	//
	//--------- utils
	//
} );
//--------------------------------------------------------------------------------------------------
//	ScrollView UI Element
//
//		A UI element for showing a scissored (cropped) view of possibly scrolled content.
//		handles user interaction (clicking and dragging to scroll,
//			events from attached scrollbar (to be implemented)), etc.
//
//	works off of basic content scroll support in Element
//		(see r_ui.js, and the "contentOffset" field in standard elements).
//	In fact, if you want a non-interactive view that clips and scrolls,
//		you can use a standard Element instead,
//		and set clipping true.
//
//	todo: once you've moved OFF the minimum drag threshold, clear it or something,
//		so we don't have this sticky space where we used to be.
//
//	todo: momentum, fling... are these done?
//
//	todo: zoom (with mouse wheel and pinch support)
//
//	TODO:  Offscreen and Dirty support?  Tricky...
//
rat.modules.add( "rat.ui.r_ui_scrollview",
[
	{name: "rat.ui.r_ui", processBefore: true },
	{name: "rat.utils.r_utils", processBefore: true },
	
	"rat.math.r_math",
	"rat.math.r_vector",
	"rat.graphics.r_graphics",
	"rat.debug.r_console",
	"rat.os.r_system",
], 
function(rat)
{
	/**
	 * @constructor
	 * @extends rat.ui.Element
	*/
	rat.ui.ScrollView = function (parentView)
	{
		rat.ui.ScrollView.prototype.parentConstructor.call(this, parentView); //	default init
		this.setClip(true);	//	scroll views aren't very interesting unless they clip
		this.lastMousePos = new rat.Vector(0, 0);
		this.grabPos = new rat.Vector(0, 0);
		this.grabOffset = new rat.Vector(0, 0);
		//	sensitivity is number of pixels per scrollWheel unit.  1 is never useful, so I'm setting it to something higher just as a guess as to what will be useful
		//	Most UI designers will want to change this (which they can do directly)
		this.wheelSensitivity = 32;
		this.wheelAffects = 'y';	//	default to wheel scrolling vertically.
		this.allowPinchZoom = false;	//	allow zoom (e.g. pinch to zoom)
		//	todo: pinch zoom tracking values.
		//		do pinch zoom/scroll like chrome does on mobile.  It's pretty slick.
		//		you can add finger touches on the fly and it handles it nicely.

		//this.allowDrag = true;

		//	fling handling
		this.allowFling = true;
		this.flingMouse = {x:0, y:0};
		this.flingTrack = [];
		for (var i = 0; i < rat.ui.ScrollView.flingTrackFrames; i++)
			this.flingTrack[i] = {x:0, y:0};
		this.flingTrackIndex = 0;
		this.flingVelocity = {x:0, y:0};
		this.flingHard = false;
		
		//	would be nice if these were based on something more useful, like pane size...?
		this.flingMaxVel = 3000;
		this.flingHardVel = 8000;
			
		//	handle all mouse events in me, even when a child claims to have handled it?
		//	This is so we can drag scrollviews full of buttons!
		//	On the other hand, we don't always want this behavior.
		//	e.g. a game world scrollview where you can drag it around
		//		but any interaction with an actor in the world
		//		doesn't result in scrollview dragging.
		//	So, if you have a scrollview full of buttons, set this flag.
		//this.flags |= rat.ui.Element.handleAllMouseInMe;
	};
	rat.utils.inheritClassFrom(rat.ui.ScrollView, rat.ui.Element);
	rat.ui.ScrollView.flingTrackFrames = 4;
	
	//	default values for scrollview objects
	rat.ui.ScrollView.prototype.elementType = "scrollView";
	
	//	drag interpretation thresholds.
	//	These default values are not super useful
	//		the appropriate thresholds depend on resolution, gui scale, size of view, desired user experience, etc.
	//		most games should override these. (like:  myView.minimumDragThreshold = 50;)
	rat.ui.ScrollView.prototype.minimumDragThreshold = 10;	//	how far before we even respect drag attempt
	rat.ui.ScrollView.prototype.meaningfulDragThreshold = 15;	//	how far before we tell everybody else we got this
	
	//	a quick way to do standard wheel-zooming support
	rat.ui.ScrollView.prototype.supportZoom = function (doSupport)
	{
		if (doSupport || doSupport === void 0)
		{
			this.wheelAffects = 'z';
			this.wheelSensitivity = 0.1;
			this.allowPinchZoom = true;
		} else {
			this.allowPinchZoom = false;
		}
	};

	//	Scroll view handle mouse wheel inside me.
	rat.ui.ScrollView.prototype.mouseWheelEvent = function (pos, ratEvent)
	{
		if (!this.isEnabled())
			return false;
		
		if (this.wheelAffects === 'y' || this.wheelAffects === 'x')
		{
			if (this.wheelAffects === 'y')
				this.contentOffset.y += ratEvent.wheelDelta * this.wheelSensitivity;
			else
				this.contentOffset.x += ratEvent.wheelDelta * this.wheelSensitivity;
			//console.log("scroll " + this.wheelEventsThisFrame);
			
			//	Make sure we haven't moved outside actual content
			this.clampScroll();
			
			//	todo: actually detect real change?
			if (this.viewChanged)
				this.viewChanged();

			return true;	//	handled
			
		} else if (this.wheelAffects === 'z')	//	zoom
		{
			//	TODO: factor in the idea that when the zoom is big, we want change to be higher.
			//		e.g. going from 9.8 to 9.9 is hardly noticeable,
			//		but going from 0.1 to 0.2 is huge.
			//		So, how should that work?
			//		It should be fixed here, not somewhere else.
			//		Remember, though, that the nice thing about a stepped zoom as currently implemented
			//		is that it's reversible.  If you switch this to something else (like percentage)
			//		make sure that's reversible, too.
			//		Interesting.  Gimp seems to use a table.
			//		scaling up goes 100%, 150, 200, 300, 400, 550, 800
			//		should be solvable ... with ... math.
			var deltaScale = ratEvent.wheelDelta * this.wheelSensitivity;
			
			this.stepZoomAnchored(deltaScale, this.mousePos);
			
			return true;
		}
		
		return false;
	};
	
	//	Zoom using an anchor point in CONTENT space.
	//	todo: move to rat.ui.Element?
	rat.ui.ScrollView.prototype.stepZoomAnchored = function(deltaScale, anchorPos)
	{
		//	Factor in position of mouse, and scroll to account for that.
		//	so we zoom like google maps and supreme commander - focusing on mouse position.
		//	The basic idea is we want the mouse to be pointing at the sme spot (in content space) when we're done.
		//	So, the easy way to do this is to remember where we were pointing, in content space,
		//	and figure out how much that content-space point moved when we scaled.
		//	deltaX = oldX * newScal - oldX * oldScale
		//	It's stupid, but it took me hours to work out.  :(
		
		//	Another problem is that we may be in the middle of a scroll at the same time,
		//	and not only will parentToLocalContentPos below give the wrong target value,
		//	but also the direct setting of offset below will be bogus, since a scroller is actively
		//	changing that value...
		//	Anyway... since we don't animate zoom right now anyway,
		//	let's just kill any scrollers we had going...  :)
		//	also, let's jump to the target immediately, so that effort wasn't lost.
		//	todo: when animated zoom is implemented, redo this.
		var targetOffset = this.getTargetContentOffset();
		this.contentOffset.x = targetOffset.x;
		this.contentOffset.y = targetOffset.y;
		rat.ui.killAnimatorsForElement(this, rat.ui.Animator.scroller);
		
		if (!anchorPos)
		{
			//	use whatever center of view currently points at.
			anchorPos = this.parentToLocalContentPos(this.place.pos.x + this.size.x / 2, this.place.pos.y + this.size.y / 2);
		}
		//	these are in pure local content space coordinates
		var oldX = anchorPos.x;
		var oldY = anchorPos.y;
		
		//	remember our old scale
		var oldZoomX = this.contentScale.x;
		var oldZoomY = this.contentScale.y;
		
		//	do the zoom
		this.stepZoom(deltaScale);
		
		//	and adjust offset (which is in pixels in parent space, I think)
		this.contentOffset.x -= oldX * this.contentScale.x - oldX * oldZoomX;
		this.contentOffset.y -= oldY * this.contentScale.y - oldY * oldZoomY;
		
		this.clampScroll();
		
		//	todo: actually detect real change?
		if (this.viewChanged)
			this.viewChanged();
	};

	//	todo: move this last pos current pos tracking stuff up to element level?
	//	could be useful for lots of classes
	//	todo: move scroll limits to element class, too?

	//	mouse down
	//	pos is in local space
	rat.ui.ScrollView.prototype.mouseDown = function (pos, ratEvent)
	{
		if (!this.isEnabled())
			return;
		
		//	all this logic in this function and related functions happens in parent space.
		//	I'm not sure why, but it seemed like a good idea at the time, and probably is.
		//	so, since mouseDown is in local space, convert to parent space for dealing with later
		this.lastMousePos.x = pos.x + this.place.pos.x;	//	last known mouse pos
		this.lastMousePos.y = pos.y + this.place.pos.y;
		this.grabPos.x = pos.x + this.place.pos.x;	//	starting grab point
		this.grabPos.y = pos.y + this.place.pos.y;
		this.grabOffset.x = this.contentOffset.x;	//	remember what offset we had when we first grabbed
		this.grabOffset.y = this.contentOffset.y;
		//console.log("sv grab " + this.grabPos.x + "," + this.grabPos.y);
		//console.log("  graboff " + this.grabOffset.x +"," +this.grabOffset.y);
		
		//	reset fling tracking
		this.flingMouse.x = rat.mousePos.x;
		this.flingMouse.y = rat.mousePos.y;
		this.flingVelocity.x = this.flingVelocity.y = 0;
		for (var i = 0; i < rat.ui.ScrollView.flingTrackFrames; i++)
			this.flingTrack[i].x = this.flingTrack[i].y = 0;
		
		rat.ui.ScrollView.prototype.parentPrototype.mouseDown.call(this, pos, ratEvent);	//	inherited behavior
	};

	//	mouse up
	//	called whether the mouseup happened in this element or not,
	//	in case we were tracking the mouse.
	//	pos is in local space
	rat.ui.ScrollView.prototype.mouseUp = function (pos, ratEvent)
	{
		var wasTracking = (this.flags & rat.ui.Element.trackingMouseDownFlag) !== 0;
		
		//	inherited behavior
		var handled = rat.ui.ScrollView.prototype.parentPrototype.mouseUp.call(this, pos, ratEvent);
		
		//	apply fling
		if (wasTracking && this.allowFling)
		{
			//	calculate average of last few frames
			var vx = vy = 0;
			for (var i = 0; i < rat.ui.ScrollView.flingTrackFrames; i++)
			{
				vx += this.flingTrack[i].x;
				vy += this.flingTrack[i].y;
			}
			vx /= rat.ui.ScrollView.flingTrackFrames;
			vy /= rat.ui.ScrollView.flingTrackFrames;
			//console.log("start fling " + vx);
			//	these defaults need to be much better, AND these values need to be overrideable.
			var MAX_FLING_VEL = this.flingMaxVel;
			var HARD_FLING_VEL = this.flingHardVel;	//	may be higher or lower than maxvel, this is just for detecting a hard attempt
			this.flingHard = false;
			
			if (vx * vx + vy * vy > HARD_FLING_VEL * HARD_FLING_VEL)
			{
				this.flingHard = true;
				console.log("hard fling " + vx + ", " + vy);
			}
			if (vx > MAX_FLING_VEL)
				vx = MAX_FLING_VEL;
			if (vx < -MAX_FLING_VEL)
				vx = -MAX_FLING_VEL;
			if (vy > MAX_FLING_VEL)
				vy = MAX_FLING_VEL;
			if (vy < -MAX_FLING_VEL)
				vy = -MAX_FLING_VEL;
			
			this.flingVelocity.x = vx;
			this.flingVelocity.y = vy;
				
			//console.log("fling! " + this.flingVelocity.x);
		}
		
		//	"handled" is better determined by parent function, which checks where mouseup happened.
		return handled;
	};

	//
	//	Handle mouse move, including passing to sub elements.
	//	This is a good time to track dragging.
	//	pos is in parent coordinates
	//
	rat.ui.ScrollView.prototype.handleMouseMove = function (newPos, handleLeaveOnly, ratEvent)
	{
		//	inherited normal func
		rat.ui.ScrollView.prototype.parentPrototype.handleMouseMove.call(this, newPos, handleLeaveOnly, ratEvent);

		if (this.flags & rat.ui.Element.trackingMouseDownFlag)
		{
			//var myBounds = this.getBounds();	//	in parent space
			//var inBounds = PointInRect(newPos, myBounds);
			//if (inBounds)
				
			var deltaX = newPos.x - this.grabPos.x;
			var deltaY = newPos.y - this.grabPos.y;
			//rat.console.log("ButtonScrollView mouse move delta = (" + deltaX + ", " + deltaY + ")");
			
			if (Math.abs(deltaX) > this.minimumDragThreshold || Math.abs(deltaY) > this.minimumDragThreshold)
			{
				//	figure out offset from original grabPos
				//	and set that offset directly.
				
				var offsetX = this.grabOffset.x + deltaX;
				var offsetY = this.grabOffset.y + deltaY;
				//console.log("scroll " + offsetX + "," + offsetY);

				this.contentOffset.x = offsetX;
				this.contentOffset.y = offsetY;

				//	todo: that bouncy snap thing when you try to drag a scrolly view past its content edges
				this.clampScroll();
				
				//	now, adjust lastmousepos only to match dx and dy,
				//	so we lock where the source position is in the scrollview and I know that made no sense but trust me it's good...
				this.lastMousePos.x = newPos.x;
				this.lastMousePos.y = newPos.y;
				
				//	todo: actually detect real change?
				if (this.viewChanged)
					this.viewChanged();
			}
			
			//	if we're being dragged, then we don't want any of our subelements,
			//	like buttons, to keep tracking this set of inputs, since it's obviously
			//	a scrollview drag.
			//	This is the behavior that used to be in buttonScrollView,
			//	but that was lame to have a whole module and class for this little bit of functionality.
			
			var threshold = this.meaningfulDragThreshold;
			if (Math.abs(deltaX) > threshold || Math.abs(deltaY) > threshold)
			{
				//console.log("canceling mouse tracking for scrollview drag with thresh " + threshold);
				// Call the stopMouseTrackingRecurse() function for all subelements of the scroll view.
				this.callForSubElements(stopMouseTrackingRecurse);
			}
		}
	};
	
	// Function for canceling mouse tracking state.
	//	(used above)
	//	todo: move to rat element class?  Generally useful?
	function doStopMouseTracking(){
		var elem = this;
		// Disable then re-enable the element.
		//	This results in lots of flags being cleared, including tracking, highlight, pressed, etc.
		// Kind of dumb, but maybe better than mucking around with internal flags?
		if (elem.isEnabled())
		{
			elem.setEnabled(false);
			elem.setEnabled(true);
		}
		//	and support a custom function to handle this case, as well, if anyone wants it.
		if (elem.stopMouseTracking)
			elem.stopMouseTracking();
	};
	
	// Function used for recursion
	function stopMouseTrackingRecurse(){
		doStopMouseTracking.call(this);
		this.callForSubElements(stopMouseTrackingRecurse);
	};
	
	//	is this view currently handling a fling?
	rat.ui.ScrollView.prototype.isFlung = function(dt)
	{
		if (!(this.flags & rat.ui.Element.trackingMouseDownFlag)
				&& (this.flingVelocity.x !== 0 || this.flingVelocity.y !== 0))
			return true;
		return false;
	};
	
	//
	//	Update every frame.  A good time to handle animation,
	//	particularly flinging.
	//
	rat.ui.ScrollView.prototype.updateSelf = function (dt)
	{
		//	get info about mouse velocity.
		if (this.allowFling)
		{
			if (this.flags & rat.ui.Element.trackingMouseDownFlag)
			{
				//	in case a mouseup is coming, track velocity for fling.
				//	average over several frames, but only a few.
				//	If the user comes to a stop and lets up, we want no velocity...
				var newPos = rat.mousePos;
				
				var dx = (newPos.x - this.flingMouse.x) / dt;
				var dy = (newPos.y - this.flingMouse.y) / dt;
				this.flingMouse.x = newPos.x;
				this.flingMouse.y = newPos.y;
				
				var spot = this.flingTrackIndex;
				this.flingTrack[spot].x = dx;
				this.flingTrack[spot].y = dy;
				this.flingTrackIndex = (spot + 1) % rat.ui.ScrollView.flingTrackFrames;
				
			//	if mouse NOT down, and we did have fling info, do fling movement update
			} else if (this.isFlung())
			{
				
				//console.log("dx " + this.flingVelocity.x);
				
				//	scroll directly
				this.contentOffset.x += this.flingVelocity.x * dt;
				this.contentOffset.y += this.flingVelocity.y * dt;
				
				this.clampScroll();
				
				//	todo: actually detect real change?
				if (this.viewChanged)
					this.viewChanged();
				
				//	this number controls how far a fling coasts...
				var decay = dt * 3000;	//	todo: make this configurable?
				//	decay velocity
				if (this.flingVelocity.x < 0)
				{
					this.flingVelocity.x += decay;
					if (this.flingVelocity.x > 0)
						this.flingVelocity.x = 0;
				} else if (this.flingVelocity.x > 0)
				{
					this.flingVelocity.x -= decay;
					if (this.flingVelocity.x < 0)
						this.flingVelocity.x = 0;
				}
				if (this.flingVelocity.y < 0)
				{
					this.flingVelocity.y += decay;
					if (this.flingVelocity.y > 0)
						this.flingVelocity.y = 0;
				} else if (this.flingVelocity.y > 0)
				{
					this.flingVelocity.y -= decay;
					if (this.flingVelocity.y < 0)
						this.flingVelocity.y = 0;
				}
			}
		}
		
		//rat.ui.ScrollView.prototype.parentPrototype.updateSelf.call(this, dt);	//	inherited behavior (there isn't one!)
	};
	
	//--------------------------------------------------------------------------------------
	//	Setup from data
	
	rat.ui.ScrollView.editProperties = [
	{ label: "scroll",
		props: [
			//	contentsize (and contentOffset) is really from rat.ui.Element, but nobody uses it but scrollview.
			//	feel free to move this if you need to.
			//	Note that the actual value is read in rat.ui.Element where it should be.
			{propName:'contentSize', type:'sizevector', valueType:'float'},
			//	There's some weird bug with setting this, so don't even allow it for now.
			//{propName:'contentOffset', type:'xyvector', valueType:'float', defValue:{x:0, y:0}},
			
			{propName:'wheelSensitivity', type:'float', defValue:32, tipText:"If used for zoom, use a really low value, like 0.1"},
			{propName:'wheelAffects', type:'string', defValue:"y"},
			{propName:'allowPinchZoom', type:'boolean', tipText:"Not supported yet!"},
			{propName:'allowFling', type:'boolean'},
		],
	}
	];

	rat.ui.ScrollView.setupFromData = function (pane, data, parentBounds)
	{
		rat.ui.data.callParentSetupFromData(rat.ui.ScrollView, pane, data, parentBounds);

		pane.wheelSensitivity = data.wheelSensitivity || pane.wheelSensitivity;
		pane.wheelAffects = data.wheelAffects || pane.wheelAffects;
		pane.allowPinchZoom = data.allowPinchZoom || pane.allowPinchZoom;
		pane.allowFling = data.allowFling || pane.allowFling;
	};
	
	//	old variant name
	rat.ui.ButtonScrollView = rat.ui.ScrollView;
	
});

//--------------------------------------------------------------------------------------------------
//
//	TOOLTIP SUPPORT
//	(heavily dependent on r_ui module, and modifies that prototype in directly,
//		but the code is grouped here for cleaner and easier editing)
//
//	Since tooltips are a basic feature of rat ui elements,
//	we mostly just attach a bunch of new functions to rat ui elements.
//	But it's convenient and cleaner to put all this code in a separate module like this.
//	r_ui is just too heavy already.
//
//	TROUBLESHOOTING:
//		Why doesn't my tooltip show up?
//			* Is your element enabled and visible?
//			* Is it the kind of element that already tracks mouse movement (e.g. button)?  If not, you'll need to explicitly call setTracksMouse(true)
//			* you either need to explicitly set an element's toolTipScreen value to the top-level screen it's in,
//				or make sure the tooltip is added AFTER the element is added to its screen.  See below.

//------------------------------------------------------------------------------------
rat.modules.add( "rat.ui.r_ui_tooltip",
[
	{ name: "rat.ui.r_ui", processBefore: true },
	"rat.utils.r_shapes",
	"rat.ui.r_ui_textbox",
],
function(rat)
{
    rat.ui.TOOL_TIP_TIME = 0.5;

	//
	//	add a tooltip update function to standard ui element prototype
	//
	rat.ui.Element.prototype.updateToolTip = function (dt)
	{
		if (!this.toolTip)
			return;
		
		if ((this.flags & rat.ui.Element.mouseInFlag)
				&& (this.flags & rat.ui.Element.visibleFlag))
		{
			this.toolTip.timer += dt;
			if (this.toolTip.timer > rat.ui.TOOL_TIP_TIME)
			{
				//console.log("show tooltip");
				//	eh... don't bother with visibility flag - they explicitly get drawn from elsewhere
				//this.toolTip.setVisible(true);

				//	if this is a mouse-tracking tooltip, update position every frame...
				if (this.toolTipPlacementFromMouse)
				{
					this.positionToolTip(this.toolTipPlacement, this.toolTipPlacementOffset, this.toolTipPlacementFromMouse);
				}

				//	convert tooltip pos to global space, in case something has moved
				var globalPos = this.getGlobalPos(this.toolTipOffset.x, this.toolTipOffset.y);
				this.toolTip.setPos(globalPos.x, globalPos.y);
				this.toolTipScreen.activeToolTip = this.toolTip;	//	set each frame - gets drawn and cleared later
			}
		} else
		{
			this.toolTip.setVisible(false);
			this.toolTip.timer = 0;
		}
	};
	
	//	TODO:  rename all the "tooltipX" variables to be inside the toolTip structure
	//
	//	See tooltip handling above: A tooltip does not draw in the normal draw sequence - 
	//	it draws on top of everything else when a screen is drawn.
	//	So, we don't add a tooltip as a subelement or anything...
	//	we just set an element's "toolTip" value for later use.

	//
	//	build an automatic text-based tooltip for this element.
	rat.ui.Element.prototype.setTextToolTip = function (text, textColor, boxColor, screen)
	{
		//console.log("setTextToolTip to " + this.name);

		if (!boxColor)
			boxColor = rat.graphics.black;
		if (!textColor)
			textColor = rat.graphics.white;

		var toolTip = new rat.ui.Shape(rat.ui.squareShape);
		//	position gets set below
		toolTip.setSize(200, 20);	//	rewritten below
		toolTip.setColor(boxColor);
		toolTip.setFrame(1, textColor);	//	use text color as frame color so it matches...

		//	A bunch of these values are just hard-coded for nice placement of a standard textbox.
		//	If you want more control, set up your own graphic (or group of graphics) and use setToolTip below.

		var tbox = new rat.ui.TextBox(text);
		tbox.setFont('calibri');
		//tbox.setFontStyle('italic');
		tbox.setTextValue(text);	//	reset to recalculate content size with font (todo: those functions should do that...)
		tbox.setColor(textColor);
		
		this.sizeTextToolTip(15, toolTip, tbox);
		
		toolTip.appendSubElement(tbox);

		this.setToolTip(toolTip, screen, 'rightHigh', { x: 6, y: 0 });	//	offset the whole thing to the right a little...

		return { container: toolTip, textBox: tbox };	//	return multiple things for client control
	};
	//	old name
	rat.ui.Element.prototype.addTextToolTip = rat.ui.Element.prototype.setTextToolTip;
	
	//	refactored function so it can be called from above, and used externally to resize text in a text tooltip...
	rat.ui.Element.prototype.sizeTextToolTip = function(fontSize, toolTip, textBox)
	{
		if (!toolTip)
			toolTip = this.toolTip;
		if (!textBox)
			textBox = toolTip.subElements[0];
		
		textBox.setFontSize(fontSize);
		
		var XBUFFERSPACING = 14;
		var YBUFFERSPACING = 4;	//	fontSize/3?
		textBox.setPos(XBUFFERSPACING / 2, YBUFFERSPACING / 2);	//	bump over and down for nicer placement within the tooltip box
		
		//	fix tooltip box to match text size
		toolTip.setSize(textBox.contentSize.x + XBUFFERSPACING, textBox.contentSize.y + YBUFFERSPACING + 2);
		
		//	also make the text box match so it's all positioned nicely
		textBox.autoSizeToContent();
	};

	//	Calculate and set position for our current tooltip
	rat.ui.Element.prototype.positionToolTip = function ( placement, offset, fromMouse )
	{
		var toolTip = this.toolTip;
		if ( !toolTip )
			return;

		if ( typeof offset === 'undefined' )
			offset = { x: 0, y: 0 };

		var tipSize = toolTip.getSize();
		var mySize = this.getSize();

		if ( fromMouse )	//	hmm... use mouse's size
		{
			mySize.x = mySize.y = 16;	//	todo better custom mouse support?
		}

		var x = 0;
		var y = 0;
		if (placement === 'none' || placement === '')	//	normal top left corner, aligned with us
		{
			x = y = 0;
		} else if (placement === 'top')	//	above, centered
		{
			x = ( mySize.x - tipSize.x ) / 2;
			y = -tipSize.y;
		} else if (placement === 'topLeft')	//	right/bottom-aligned with our top-left corner.
		{
			x = -tipSize.x;
			y = -tipSize.y;
		} else if (placement === 'topRight')	//	upper right corner
		{
			x = mySize.x;
			y = -tipSize.y;
		} else if (placement === 'bottom')	//	below, centered
		{
			x = ( mySize.x - tipSize.x ) / 2;
			y = mySize.y;
		} else if (placement === 'bottomLeft')	//	aligned to bottom left corner
		{
			x = -tipSize.x;
			y = mySize.y;
		} else if ( placement === 'bottomRight' )	//	aligned to bottom right corner
		{
			x = mySize.x;
			y = mySize.y;
		} else
		{	//default to 'rightHigh' which means on the right, but shifted up artistically (1/3)
			x = mySize.x;
			y = mySize.y / 3 - tipSize.y / 2;	//	align tip vertical center with a high point inside my height
		}

		if ( fromMouse && this.mousePos )	//	now adjust if we're supposed to track mouse pos, if we have a mouse pos right now
		{
			x += this.mousePos.x;
			y += this.mousePos.y;
		}

		toolTip.setPos( x + offset.x, y + offset.y );	//	position relative to this element's location

		//	store original placement info in case our bounds change and we need to recalculate
		this.toolTipPlacement = placement;
		this.toolTipPlacementOffset = { x: offset.x, y: offset.y };
		this.toolTipPlacementFromMouse = fromMouse;

		this.toolTipOffset = toolTip.getPos().copy();	//	remember our tooltip's calculated position for simplicity later
	};
	
	/**
	 * Set this element as our current tooltip.
	 * could be anything - textbox, image, whatever.  For an easier text-only function, use setTextToolTip
	 * @param {Object} toolTip
	 * @param {Object} screen
	 * @param {string} placement
	 * @param {Object=} offset
	 * @param {boolean=} fromMouse
	 */
	rat.ui.Element.prototype.setToolTip = function (toolTip, screen, placement, offset, fromMouse)
	{
		if (typeof offset === 'undefined')
			offset = { x: 0, y: 0 };

		this.toolTip = toolTip;	//	set tooltip

		//	positioning logic...
		this.positionToolTip(placement, offset, fromMouse);

		toolTip.setVisible(false);
		toolTip.timer = 0;

		//	If we weren't given a screen object, find our top-most parent automatically.
		//	note that this means the element must already be added to the tree when this function is called!
		//	but see "assumed" flag below
		if (!screen)
		{
			screen = this.getTopParent();
			
			//	Keep track of whether a screen was explicitly specified here,
			//		and if we weren't given one here, and couldn't find one here (because we weren't added to the tree yet)
			//		then set our toolTipScreen later when we ARE added to the tree.
			//	This is less of a problem these days now that I try to specify parent in constructor call,
			//		so most elements are correctly in the tree right away.
			this.toolTipScreenWasAssumed = true;
		}
		//	todo: look into this - does a loop of references like this mess up garbage collection?
		//	we're already a child of the screen - does pointing at our top parent like this cause trouble?
		//	it shouldn't!
		this.toolTipScreen = screen;	//	need this for adding to draw list later
		
		//	Usually, tooltips only make sense if we use mouse tracking on this object.
		//	So, let's assume they want that, here.
		this.setTracksMouse(true);	//	for tooltips to work
	};

	//
	//	return current tooltip container. May be undefined or null, if one hasn't been set.
	rat.ui.Element.prototype.getToolTip = function ()
	{
		return this.toolTip;
	};
	
} );
//--------------------------------------------------------------------------------------------------
//	edittext (editable text box)
//

/*
	This approach does our own full handling of everything instead of a DOM object.
	crazy, I know, but...

	REASONS for doing this ourselves instead of using DOM:
		* It's hard to make it look consistent - different browsers and hosts (e.g. win10) use different visual borders, fonts, sizes, rendering
		* Can't control draw order - it's either in front of the whole canvas or behind it.
		* It won't work at all in places like Wraith where there's no DOM
		
	Note about the rat debug console text entry system...
		We used that as reference, but didn't consolidate code.  rat.console doesn't want to know anything about rat UI,
		we need to be able to use the console with as few other systems loaded as possible.  So, we left all that code there,
		and didn't refactor.
		
	Notes on Dirty Flags and offscreen rendering for edit text.
		The dirty flag will change frequently as text gets edited...
			Things like text changing, cursor movement, panning, and even just a blinking carat mean that
			the display needs to be updated.
			However, it's unlikely to update faster than a few times a second,
			so if you still want to use offscreen rendering, that's understandable.
		And for visual changes when highlighting, we use flagsThatDirtyMe, which is what it's for.
		Note that currently, the clipping that happens with offscreened elements makes our highlighting
			not look quite as good...  :(
		In order to fix that, we'd need to change our drawing to inset text a bit,
			to make room for borders drawn INSIDE our space.

	My design target here is basically standard Windows edit text boxes.

	States:
		An edit text box can be highlighted but not actively editing.
			for example, let's say you're typing in one text box, but you move the mouse over another one.
			It needs to highlight to show you can click on it, but the current edittext is still the place where keystrokes go.
			
		Highlighted:
			Moused over, or navigated to in another way
		
		Targeted:
			This is the current input target in an input map (is that all this means?)
			Whenever we're targeted, we'll also be highlighted, I think.
			But not necessarily active.  E.g. you can tab through text boxes,
			and hit ENTER to actually start editing one.
			
			This needs thinking through.  "targeted" may not currently be any more useful than highlighted.
			
		Active:
			Actively being edited, has a blinking carat, etc.
		
	TODO
		
		* Configurable colors for everything
			it's semi-done now, with fields to set, but a client would have to know what to set!
			and we might want to regroup them and rename fields.  Maybe one set of "looks",
			and client can set some and we auto-fill the rest.
			That'd be nice.  setLook() function
		* update pan on bounds change event.  How likely is that?
		
		* Support mid/right aligned text!  Not currently working right, in several ways.
			but oh man, I have been working on this for a couple of days now, and I'm tired of it.
			left-alignment works great.  :)
			
		* ctrl-A should select all.  I use it commonly, and it really bugs me that it doesn't work!
		
		* ctrl-left and right arrows (move word to word) (see how other editors do it - spaces are tricky)
		* shift-ctrl-left and right arrows (extend selection while moving word to word)
		
		* re-test clipboard - it was broken on ctrl-x?
		
		* Rethink pan behavior in some cases?  When you're deleting and cursor is at left edge of box, can't see what you're deleting.
		
	More advanced stuff:
	
		* select text
			* display selection box
			* cursor still has an independent position, and still displays
			* mouse selection
			* shift-arrows
			* shift-home, shift-end
			* typing replaces entire selection
			* tabbing to field selects all text
			* clicking on inactive field selects all text
			* click and drag to select text
			* shift-click to select text
			
			* copy (at the very least, control-C into internal buffer)
			
		* paste (replace selected, insert, etc.)

		* special handling of enter key, like callback? (many use cases will need it)
		* option for handle enter key - call trigger
		* special handling of successful text changes, like callback?

		* control-arrow - skip ahead a whole word
		
		* double-click to select word
			(and then put in selecting-words mode for continued dragging)
		* triple-click to select whole line
		
		* multi-line editing
			* ctrl-enter to put in carriage-return
		
		* support indenting text a little from edge - it's a little hard to read with a frame.
			probably do this in textbox module.
		
		* unicode text entry... is that working somehow?
		* password character mode
		* text filter rules, like
			only allowing numbers or a defined character set
			only allowing a certain length
		* right-to-left text support for things like arabic, depending on font
*/

rat.modules.add( "rat.ui.r_ui_edittext",
[
	{name: "rat.ui.r_ui_textbox", processBefore: true },
	
	"rat.os.r_clipboard",
	
	//{name: "rat.utils.r_utils", processBefore: true },
	//"rat.graphics.r_graphics",
	//"rat.utils.r_wordwrap",
	//"rat.debug.r_console",
	//"rat.ui.r_ui_data",
], 
function(rat)
{
	/**
	 * @constructor
	 * @extends rat.ui.TextBox
	 * like textbox, can construct with parent,value or just one, or neither
	*/
	rat.ui.EditText = function(arg1, arg2)
	{
		//	default configuration
		this.config = {};
		this.config.enters = [
			{action: 'toggleActive'},	//	normal enter with NO MODIFIERS toggles active
			{ctrlKey : true, action: 'insertCR'},	//	ctrl-enter inserts \n
			//	the rest are passed up unhandled
		];
		this.config.maxLength = -1;	//	unlimited text length
		
		//	by default, we expect to be small, and need to clip and pan
		//	set up pan before constructor, since it calls other functions that update pan
		this.supportPan = true;
		this.pan = {x:20, y:0};
		
		rat.ui.EditText.prototype.parentConstructor.call(this, arg1, arg2); //	default init
		
		this.setTracksMouse(true);	//	we do want mouse tracking, highlighting, etc.
		this.setTargetable(true);	//	we're targetable for key input
		
		this.editFrameLook = {
			normalColor : new rat.graphics.Color(180, 180, 180),
			highlightColor : new rat.graphics.Color(255, 255, 180),
			activeColor : new rat.graphics.Color(255, 255, 255),
			lineWidth : 2,
		};
		this.editFillLook = {
			activeColor : new rat.graphics.Color(20, 20, 20, 0.2),	//	very slight box background when editing
		};
		this.editSelectionLook = {
			normalColor : new rat.graphics.Color(200, 200, 180, 0.2),	//	selection when inactive - very light
			activeColor : new rat.graphics.Color(255, 255, 180, 0.8),
			caratColor : new rat.graphics.Color(255, 255, 255),
			caratWidth : 2,
			caratBlinkSpeed : 2,	//	on-blinks per second
		};
		
		this.timer = 0;
		this.active = false;	//	being edited?
		
		this.overrideMaxWidth(-1);	//	use a huge maxwidth so the text doesn't squish
		
		//	set up cursor pos based on initial value
		var cursorPos = 0;
		if (this.value)
			cursorPos = this.value.length;
		else	//	a lot of things below are easier if we assume there's some sort of value
			this.value = "";
		this.setCursorPos(cursorPos);
		
	};
	rat.utils.inheritClassFrom(rat.ui.EditText, rat.ui.TextBox);
	rat.ui.EditText.prototype.elementType = 'editText';
	
	//	these flag changes need to set me dirty, because they change my look!
	//	see rat.ui module, comments above flagsThatDirtyMe variable,
	//	and rat.ui.Element.prototype.checkFlagsChanged function.
	//	This is similar to buttons...
	rat.ui.EditText.prototype.flagsThatDirtyMe = 
			rat.ui.Element.highlightedFlag |
			rat.ui.Element.enabledFlag

	//	set how we should handle enter key.
	//	pass in a list of behaviors like this:
	//		[
	//			{action: 'toggleActive'},	//	normal enter toggles active state
	//			{ctrlKey: true, altKey: true, action: 'insertCR'},	//	ctrl-alt-enter inserts CR
	//			//	other combinations are unhandled
	//		]
	//
	//	you can also pass in a function to call, like this:
	//		setEnterHandling(myFunction);
	//	or, more complicated case:
	//		setEnterHandling([{action:myFunction}, {ctrlKey:true, action:myOtherFunction}]);
	//	the function gets passed element, value, ratEvent
	//
	//	or pass empty array or undefined to do no handling of enter key at all.
	//	or pass a single string action and we'll make that the behavior for standard enter, and no others will be handled.
	//		(e.g. setEnterHandling('toggleActive');
	//
	rat.ui.EditText.prototype.setEnterHandling = function(enters)
	{
		//	basic arguments not in above format?  That's OK - we'll arrange things.
		if (typeof(enters) === 'string')
			enters = {action:enters};
		else if (typeof(enters) === 'function')
			enters = {action:enters};
		//	handle empty arg
		if (!enters)
			enters = [];
		//	make sure it's in array form
		if (!Array.isArray(enters))
			enters = [enters];
		
		this.config.enters = enters;
	};
	
	//	max text width in characters.
	//	-1 means no limit.
	rat.ui.EditText.prototype.setMaxTextLength = function(maxLength)
	{
		this.config.maxLength = maxLength;
	};
	
	//	override set value to fix internal tracking
	//	This function is generally used to replace the whole text value,
	//	so go ahead and move cursor to end.
	//	This is different from textChanged() which is called any time text changes even just a little.
	rat.ui.EditText.prototype.setTextValue = function(text)
	{
		rat.ui.EditText.prototype.parentPrototype.setTextValue.call(this, text);	//	inherited
		var cursorPos = 0;
		if (text)
			cursorPos = text.length;
		this.setCursorPos(cursorPos);
	};
	
	//	override text changed to also recalculate pan
	rat.ui.EditText.prototype.textChanged = function ()
	{
		//	restrict text here.
		//	Is this a good place to do it?
		//	This will cover external setTextValue calls as well as internal insertText calls,
		//	so it seems OK.
		//	todo: filter out disallowed characters (e.g. for a numbers-only field)
		//	TODO: there are bugs.  e.g. typing full width + 1, next backspace is ignored.
		//	so, basically cursor is incorrectly adapting to changed text.
		//	but isn't it about to get adjusted after this function returns, usually?  Look into this.
		
		if (this.config.maxLength > 0 && this.value.length > this.config.maxLength)
		{
			this.value = this.value.substring(0, this.config.maxLength);
		}
		
		if (this.textChangeCallback)
			this.textChangeCallback(this, this.value);
		
		rat.ui.EditText.prototype.parentPrototype.textChanged.call(this);	//	inherited
		
		this.updatePan();
	};
	
	//	set cursor to a new position
	//	this has a few side effects like reset cursor blinking,
	//	and reset selection range, if there was one.
	//	If you need to set cursor pos without changing selection range, don't use this function.
	rat.ui.EditText.prototype.setCursorPos = function(pos)
	{
		this.cursorPos = pos;
		this.selectionAnchor = pos;
		this.selectionEnd = pos;
		this.selectionLeft = pos;
		this.selectionRight = pos;
		this.timer = 0;		
		this.setDirty(true);
		this.updatePan();
	};
	
	rat.ui.EditText.prototype.selectAll = function()
	{
		this.cursorPos = this.value.length;
		this.selectionAnchor = 0;
		this.selectionLeft = 0;
		this.selectionEnd = this.value.length;
		this.selectionRight = this.value.length;
		this.timer = 0;
		this.setDirty(true);	//	cursor/selection may have changed
		this.updatePan();
	};
	
	//	we actively maintain selectionLeft and selectionRight for everyone's convenience,
	//	so we don't have to keep checking the order of anchor and end
	rat.ui.EditText.prototype.updateSelectionLeftRight = function()
	{
		if (this.selectionAnchor < this.selectionEnd)
		{
			this.selectionLeft = this.selectionAnchor;
			this.selectionRight = this.selectionEnd;
		} else {
			this.selectionLeft = this.selectionEnd;
			this.selectionRight = this.selectionAnchor;
		}
		
	};
	
	//	update pan to match edited text and width.
	//	We want to show the cursor, wherever it is.
	//	NOTE:  I have no particular faith in all this math and various condition checking.
	//		It could definitely all be rewritten in a better way.
	//		It does seem to work.
	rat.ui.EditText.prototype.updatePan = function()
	{
		if (this.cursorPos === 0)
		{
			//	simple case - always slam left when cursor is at the start of the string.
			this.pan.x = 0;
			return;
		}
		rat.graphics.ctx.font = this.fontDescriptor;	//	for string measurement
		var s = this.value.substring(0, this.cursorPos);
		var metrics = rat.graphics.ctx.measureText(s);
		var targetX = metrics.width;
		
		//	base new pan partly on where old pan was - only move as much as needed.
		//	This is tricky, because we also don't ever want to show space to the right past the end of our text,
		//	with text being cut off at left.
		
		var newPan;
		
		//	does the old pan work?
		if (this.pan.x < targetX - 4 && this.pan.x + this.size.x > targetX + 4)
		{
			//	keep it, except if it means there's space after the end of our string,
			//	which is silly...
			if (this.pan.x + this.size.x + 4 > this.textWidth)
				newPan = this.textWidth - this.size.x + 4;	//	get end of string in view
			else
				newPan = this.pan.x;
		} else {	//	the old pan doesn't work
			//	this will put the cursor on the right edge
			newPan = targetX - this.size.x + 4;	//	a little extra space so we can see cursor
			if (this.pan.x > newPan)	//	we're panning left..., so pick a spot that puts the cursor on the left.
				newPan = targetX - 4;	//	a little extra space so we can see cursor
		}
		
		this.pan.x = newPan;
		//	but never pan farther left than the start of the string
		if (this.pan.x < 0)
			this.pan.x = 0;
		//	don't bother setting dirty - assume whatever changed our text already did that.
	};
	
	//	Set me active or inactive
	//	activity controls things like blinking cursor, selection display, etc.
	rat.ui.EditText.prototype.setActive = function(active)
	{
		if (active != this.active)
			this.setDirty(true);
		this.active = active;
		if (!this.active)
			this.caratBlinkOn = false;
		else
			this.timer = 0;	//	reset carat blink timer
	};
	
	//	draw frame to show this is editable text, and to help indicate state
	rat.ui.EditText.prototype.drawFrame = function(ctx)
	{
		//	our edit text frame ("editFrame") is separate from rat ui "frame" concept for a couple of reasons:
		//		* it has several states, so needs more info, like multiple colors
		//		* might want to be fancy about how we draw it, like make it look slightly shadowed or something
		var frameLook = this.editFrameLook;
		ctx.lineWidth = frameLook.lineWidth;
		
		var outset = 0;
		
		var targeted = (this.flags & rat.ui.Element.targetedFlag) !== 0;
		var highlighted = (this.flags & rat.ui.Element.enabledFlag) && (this.flags & rat.ui.Element.highlightedFlag);
		
		if (this.active)
			ctx.strokeStyle = frameLook.activeColor.toString();
		else if (highlighted)
			ctx.strokeStyle = frameLook.highlightColor.toString();
		else
			ctx.strokeStyle = frameLook.normalColor.toString();
		
		//	still feeling this out.  Feel free to change it.
		if (highlighted || targeted)
		{
			ctx.lineWidth = frameLook.lineWidth + 1;
			outset = 1;
		}
		
		//	draw frame		
		ctx.strokeRect(-this.center.x - outset, -this.center.y - outset,
					this.size.x + 2 * outset, this.size.y + 2 * outset);
		
	};
	
	//	override draw to draw carat and selection, etc.
	rat.ui.EditText.prototype.drawSelf = function()
	{
		var ctx = rat.graphics.getContext();
		
		//	draw a background
		if (this.active)
		{
			ctx.fillStyle = this.editFillLook.activeColor.toString();
			ctx.fillRect(-this.center.x, -this.center.y, this.size.x, this.size.y);
		}
		//	and a frame
		this.drawFrame(ctx);
		
		//	pan support
		var panned = false;
		if (this.supportPan)
		{
			panned = true;
			ctx.save();
			
			//	panning includes clipping
			//	clip to a slightly smaller space than our box, for looks.
			//	would be nice to clip a little more, but we really need to add support for text inset, first.
			var inset = 1;
			ctx.beginPath();
			//	todo: factor in center values
			ctx.rect(inset -this.center.x, inset -this.center.y, this.size.x - inset*2, this.size.y - inset*2);
			ctx.clip();
			
			ctx.translate(-this.pan.x, -this.pan.y);
		}
		
		//	inherited behavior (draw text)
		rat.ui.EditText.prototype.parentPrototype.drawSelf.call(this);
		
		//	draw selection
		var selectionLook = this.editSelectionLook;
		var alignX = this.setupAlignX(ctx, this.textWidth);
		if (this.selectionAnchor !== this.selectionEnd)
		{
			var y = -this.center.y + 2;
			var h = this.size.y - 4;
			
			var x1 = alignX;
			var s = this.value.substring(0, this.selectionAnchor);
			//rat.graphics.ctx.font = this.fontDescriptor; //	already done for draw above
			var metrics = rat.graphics.ctx.measureText(s);
			x1 += metrics.width;
			
			var x2 = alignX + 1;	//	select a little bit farther...
			s = this.value.substring(0, this.selectionEnd);
			//rat.graphics.ctx.font = this.fontDescriptor; //	already done for draw above
			metrics = rat.graphics.ctx.measureText(s);
			x2 += metrics.width;
			var w = x2 - x1;
			
			if (this.active)
				ctx.fillStyle = selectionLook.activeColor.toString();
			else
				ctx.fillStyle = selectionLook.normalColor.toString();
			ctx.fillRect(x1, y, w, h);
		}
		//	draw carat
		var caratBlinkOn = this.caratBlinkOn;
		if (this.active && caratBlinkOn)
		{
			//	where do we draw the carat?
			var x = alignX + 1;	//	bump slightly so we can see it just inside the left edge of an empty box, which is a common case
			//var y = this.setupAlignY(ctx);
			//var h = this.fontLineHeight;
			var y = -this.center.y + 2;
			var w = selectionLook.caratWidth;
			var h = this.size.y - 4;
			
			//var width = 0;
			//if (this.lineWidths)
			//	width = this.lineWidths[0];
			//var width = this.getTextWidth();
			//	OK, now that we have a cursorpos, it's a little more complicated.
			//	We need to know the width of this particular part of the string.
			var s = this.value.substring(0, this.cursorPos);
			//rat.graphics.ctx.font = this.fontDescriptor; //	already done for draw above
			var metrics = rat.graphics.ctx.measureText(s);
			var width = metrics.width;
			
			x += width;
			
			ctx.fillStyle = selectionLook.caratColor.toString();
			ctx.fillRect(x, y, w, h);
		}
		
		//	undo pan and clip
		if (panned)
		{
			ctx.restore();
		}
	};
	
	rat.ui.EditText.prototype.updateSelf = function(dt)
	{
		if (this.active)
		{
			this.timer += dt;
			
			var oldCaratBlink = this.caratBlinkOn;
			this.caratBlinkOn = ((this.timer * this.editSelectionLook.caratBlinkSpeed * 2) % 2) < 1;
			if (oldCaratBlink != this.caratBlinkOn)
				this.setDirty(true);
		}
	};
	
	//	handle keys
	rat.ui.EditText.prototype.keyDown = function(ratEvent)
	{
		var which = ratEvent.which;
		
		//	Don't eat F keys
		if (ratEvent.which >= rat.keys.f1 && ratEvent.which <= rat.keys.f12)
			return false;
		
		else if (ratEvent.which === rat.keys.esc)
		{
			if (this.active)
			{
				this.setActive(false);
				return true;
			} else
				return false;
		}
		
		//	Much more flexible enter-key handling, according to configuration.
		//	What enter-key behaviors are allowed, and what do they do?
		//	For instance, you can say ctrl-enter adds \n, or you can say nothing does.
		//	see constructor for defaults and setEnterHandling() for how to change it
		if (ratEvent.which === rat.keys.enter)
		{
			for (var i = 0; i < this.config.enters.length; i++)
			{
				var ent = this.config.enters[i];
				
				if (!!ratEvent.sysEvent.ctrlKey === !!ent.ctrlKey
					&& !!ratEvent.sysEvent.shiftKey === !!ent.shiftKey
					&& !!ratEvent.sysEvent.altKey === !!ent.altKey)
				{
					if (typeof(ent.action) === 'function')	//	direct function call desired
					{
						ent.action(this, this.value, ratEvent);
						
					} else if (ent.action === 'toggleActive')
					{
						if (this.active)
							this.setActive(false);
						else
							this.setActive(true);
					} else if (ent.action === 'insertCR')
					{
						//	Note:  This is not really well-supported right now.
						//	It handles input correctly, but doesn't render (pan) correctly,
						//	or handle cursor navigation well.  :(
						this.insertText("\n");
						//	fix cursor pos?
					}
					//	todo: support passing up unhandled?
					return true;
				}
			}
		}
		
		//	some key handling is only if we're active...
		if (this.active)
		{
			var editText = this;
			var delChar = function (index)
			{
				var a = editText.value.slice(0, index);
				var b = editText.value.slice(index + 1, editText.value.length);
				editText.value = a + b;
				editText.textChanged();
			}
			var delSelection = function (index)
			{
				var a = editText.value.slice(0, editText.selectionLeft);
				var b = editText.value.slice(editText.selectionRight, editText.value.length);
				editText.value = a + b;
				editText.textChanged();
			}
			
			//	some behavior below is affected by modifier keys
			var shiftKey = ratEvent.sysEvent.shiftKey;
			
			//	for navigation key handling
			//	track if we handled anything.  If we did, do all of our cursor/selection bounds checking at once below,
			//	and return correct handled flag.
			//	In many cases we just do our changes and return immediately.  this "handled" and cursor updating is really just for navigation...
			//	maybe separate that out to a separate function for clarity...?
			var handled = false;
			var newCursorPos = this.cursorPos;
			
			if (ratEvent.which === rat.keys.c && ratEvent.sysEvent.ctrlKey)
			{
				var value = editText.value.slice(editText.selectionLeft, editText.selectionRight);
				if (value && value.length > 0)
				{
					rat.clipboard.store('text', value);
					console.log("copy " + value);
				}
				return true;
			}
			else if (ratEvent.which === rat.keys.v && ratEvent.sysEvent.ctrlKey)
			{
				var value = rat.clipboard.retrieve('text');
				if (value)
				{
					console.log("paste " + value);
					this.insertText(value);
				}
				return true;
			}
			else if (ratEvent.which === rat.keys.x && ratEvent.sysEvent.ctrlKey)
			{
				var value = editText.value.slice(editText.selectionLeft, editText.selectionRight);
				if (value && value.length > 0)
				{
					rat.clipboard.store('text', value);
					console.log("cut " + value);
					this.insertText("");
				}
				return true;
			}
			else if (ratEvent.which === rat.keys.backspace)
			{
				//	suppress default handling so backspace doesn't navigate back in browser.
				ratEvent.sysEvent.preventDefault();
				if (this.selectionEnd != this.selectionAnchor)
				{
					delSelection();
					newCursorPos = this.selectionLeft;
				}
				else if (this.cursorPos > 0)
				{
					delChar(this.cursorPos-1);
					newCursorPos = this.cursorPos-1;
				}
				shiftKey = false;	//	ignore shift key in this case
				handled = true;
			} else if (ratEvent.which === rat.keys.del)
			{
				if (this.selectionEnd != this.selectionAnchor)
				{
					delSelection();
					newCursorPos = this.selectionLeft;
				}
				else if (this.cursorPos < this.value.length)
				{
					delChar(this.cursorPos);
					//	leave cursorpos where it is
				}
				shiftKey = false;	//	ignore shift key in this case
				handled = true;
			} else if (ratEvent.which === rat.keys.leftArrow)
			{
				//	different editors handle this differently.
				//	This is not standard windows, which moves to anchor and then -1,
				//	But this is like notepad++, which moves to left side of selection.
				if (!shiftKey && this.selectionEnd != this.selectionAnchor)
					newCursorPos = this.selectionLeft;
				else
					newCursorPos = this.cursorPos - 1;
				handled = true;
			} else if (ratEvent.which === rat.keys.rightArrow)
			{
				if (!shiftKey && this.selectionEnd != this.selectionAnchor)
					newCursorPos = this.selectionRight;
				else
					newCursorPos = this.cursorPos + 1;
				handled = true;
			} else if (ratEvent.which === rat.keys.home)
			{
				newCursorPos = 0;
				handled = true;
			} else if (ratEvent.which === rat.keys.end)
			{
				newCursorPos = this.value.length;
				handled = true;
			}
			//	some special char support...?
			
			if (handled)
			{
				newCursorPos = Math.max(newCursorPos, 0);
				newCursorPos = Math.min(newCursorPos, this.value.length);
				
				//	shift key modifier - shrink or extend selection with cursorpos
				//	(see if cursorpos aligned with either begin or end, and if it moved, move begin or end accordingly)
				if (shiftKey)
					this.selectionEnd = newCursorPos;
				else
					this.selectionEnd = this.selectionAnchor = newCursorPos;
				
				this.updateSelectionLeftRight();
				
				this.cursorPos = newCursorPos;
				
				this.updatePan();
				
				this.timer = 0;	//	reset carat timer whenever carat moves
				this.setDirty(true);	//	technically, it's possible nothing changed, but I'm not that picky here.
				
				return true;
			}
		//	some inactive handling...
		} else {
			//	let's turn backspace into a clear,
			//	because it sucks to have backspace go back in browser history.
			if (ratEvent.which === rat.keys.backspace || ratEvent.which === rat.keys.del)
			{
				//	suppress default handling so backspace doesn't navigate back in browser.
				ratEvent.sysEvent.preventDefault();
				this.setTextValue("");
				return true;
			}
		}
		
		return false;
	};
	
	rat.ui.EditText.prototype.keyPress = function(ratEvent)
	{
		if (!this.active)
			return false;
				
		var char = ratEvent.sysEvent.char;
		//console.log("char " + char);
		if (char.charCodeAt(0) < ' '.charCodeAt(0))	//	unprintable character
			return false;
		if (char !== "\n" && char !== "\t")
		{
			this.insertText(char);
		}
		return true;
	};
	
	//	insert this text wherever insertion point (or selection) is.
	//	This is generally a good chokepoint for controlling what gets set as our text.
	rat.ui.EditText.prototype.insertText = function(value)
	{
		//	replace entire selection.
		var a = this.value.slice(0, this.selectionLeft);
		var b = this.value.slice(this.selectionRight, this.value.length);
		
		this.value = a + value + b;
		this.textChanged();
		
		var newCursorPos = this.selectionLeft + value.length;
		this.setCursorPos(newCursorPos);
	};
	
	//	make sure when losing focus that we become inactive if we were!
	rat.ui.EditText.prototype.blur = function()
	{
		this.setActive(false);
		return rat.ui.EditText.prototype.parentPrototype.blur.call(this); //	inherited
	};
	
	rat.ui.EditText.prototype.handleTabbedTo = function(sourceEvent)
	{
		this.setActive(true);
		this.selectAll();
	};
	
	//	convert x value to a character index,
	//	e.g. for clicking on text.
	//	This will pick left or right of a character depending on how far left/right of center you are
	//	Note that this is pretty dang expensive right now, with many calls to measureText(), but we assume that's OK.
	rat.ui.EditText.prototype.pointToCharIndex = function(pos)
	{
		rat.graphics.ctx.font = this.fontDescriptor; //	for measuring
		var bestIndex = this.value.length;
		var lastWidth = 0;
		for (var i = 1; i <= this.value.length; i++)
		{
			var s = this.value.substring(0, i);
			var metrics = rat.graphics.ctx.measureText(s);
			var dWidth = metrics.width - lastWidth;
			if (pos.x + this.center.x + this.pan.x < lastWidth + dWidth/2)
			{
				bestIndex = i-1;
				break;
			}
			lastWidth = metrics.width;
		}
		return bestIndex;
	};
	
	//	like windows ui systems, go active immediately on mousedown,
	//	unless we're already active, in which case start selecting.
	rat.ui.EditText.prototype.mouseDown = function(pos, ratEvent)
	{
		//	call inherited to get correct flags cleared/set
		rat.ui.EditText.prototype.parentPrototype.mouseDown.call(this, pos, ratEvent);
		
		if (!this.active)
		{
			//rat.aeb = this;	debug
			this.setActive(true);
			if (this.config.selectAllOnClickActivate)
				this.selectAll();
			else
			{
				var newIndex = this.pointToCharIndex(pos);
				this.setCursorPos(newIndex);
			}
			
		} else {
			
			var newIndex = this.pointToCharIndex(pos);
			
			if (ratEvent.sysEvent.shiftKey)
			{
				this.selectionEnd = newIndex;
				this.updateSelectionLeftRight();
			}
			else
				this.selectionEnd = this.selectionAnchor = this.selectionLeft = this.selectionRight = newIndex;
			
			this.cursorPos = newIndex;
		}
		
		this.trackingClick = true;
		
		this.timer = 0;
		this.setDirty(true);	//	cursor/selection may have changed
		
		return true;
	};
	
	rat.ui.EditText.prototype.mouseUp = function(pos, ratEvent)
	{
		//	call inherited to get correct flags cleared/set
		rat.ui.EditText.prototype.parentPrototype.mouseUp.call(this, pos, ratEvent);
		
		if (this.trackingClick)
		{
			this.trackingClick = false;
			return true;
		}
		
		return false;
	};
	
	//	track mouse movement for selection
	rat.ui.EditText.prototype.mouseMove = function(pos, ratEvent)
	{
		//	note that we don't care if cursor is really in our bounds.
		//	It's OK to drag outside our space, as long as it was all
		//	from a click that started in our space.
		if (this.trackingClick)
		{
			var newIndex = this.pointToCharIndex(pos);
			if (newIndex != this.selectionEnd)
				this.setDirty(true);
			this.selectionEnd = newIndex;
			this.cursorPos = newIndex;
			this.updateSelectionLeftRight();
			this.updatePan();
			return true;
		}
		return false;
	};
	
	//	for keyboard/controller support, also go active on trigger (action button) if not already active
	rat.ui.EditText.prototype.trigger = function()
	{
		if (!this.active)
		{
			this.setActive(true);
			this.selectAll();
		}
		return rat.ui.EditText.prototype.parentPrototype.trigger.call(this); //	inherited trigger
	};
	
	// Support for creation from data
	//	TODO: support all the config stuff, like max width, enter key handling, etc.
	rat.ui.EditText.setupFromData = function(pane, data, parentBounds)
	{
		rat.ui.data.callParentSetupFromData( rat.ui.EditText, pane, data, parentBounds );
	};
});
//--------------------------------------------------------------------------------------------------
//
//	Fillbar ui element
//
//	TODO:  Offscreen and Dirty support
//
rat.modules.add( "rat.ui.r_ui_fillbar",
[
	{name: "rat.utils.r_utils", processBefore: true },
	{name: "rat.ui.r_ui", processBefore: true },
	{name: "rat.ui.r_ui_fill", processBefore: true },
	
	"rat.debug.r_console",
	"rat.ui.r_ui_data",
	"rat.graphics.r_graphics",
	"rat.math.r_math",
	"rat.ui.r_ui_data",
], 
function(rat)
{
	/**
	 * @constructor
	 * @extends rat.ui.Element
	*/
	rat.ui.FillBar = function ()
	{
		rat.ui.FillBar.prototype.parentConstructor.call(this); //	default init
		
		//	Default to a fill style/shape supported by this class
		this.fillStyle = 'fill';
		this.fillShape = 'rectangle';
		
		this.minColor= rat.graphics.red;
		this.maxColor= rat.graphics.green;
		this.backColor = rat.graphics.black;
		this.frameColor = rat.graphics.blue;
	};
	rat.utils.inheritClassFrom(rat.ui.FillBar, rat.ui.Fill);
	rat.ui.FillBar.prototype.elementType = "fillBar";

	//	Set the colors that we will use to draw.
	rat.ui.FillBar.prototype.setColors = function (backColor, bodyColor, frameColor)
	{
		//	If instead of a single color for the body, we were given two
		this.backColor = backColor;
		this.minColor = bodyColor.min || bodyColor;
		this.maxColor = bodyColor.max || bodyColor;
		this.frameColor = frameColor;
	};

	//	Draw the fill bar
	rat.ui.FillBar.prototype.drawSelf = function ()
	{
		var ctx = rat.graphics.ctx;
		ctx.save();

		//	Figure out the color
		var percent = this.percent;
		if (percent < 0)
			percent = 0;
		else if (percent > 1)
			percent = 1;
		var color = new rat.graphics.Color();
		if (this.minColor.r !== this.maxColor.r ||
			this.minColor.g !== this.maxColor.g ||
			this.minColor.b !== this.maxColor.b ||
			this.minColor.a !== this.maxColor.a)
		{
			rat.graphics.Color.interp(this.minColor, this.maxColor, this.percent, color);
		}
		else
			color = this.minColor;

		var w, h;
		w = this.getWidth();
		h = this.getHeight();
		
		//	UP is assumed to be 0.   Pull this from data
		//	Assuming clockwise fill.   Pull this from data
		//	Assuming left->right.   Pull from data.
		var startAngle = -rat.math.HALFPI;
		var endAngle = startAngle + (rat.math.PI2 * this.percent);

		var cx = this.size.x / 2 + this.center.x;
		var cy = this.size.y / 2 + this.center.y;
		var r;

		//	If we are radial, then handle that
		if (this.fillShape === "circle")
		{
			if (w<h)
				r = w;
			else
				r = h;
			
			//	BG
			ctx.fillStyle = this.backColor.toString();
			ctx.beginPath();
			ctx.arc(0, 0, r, 0, rat.math.PI2, false);

			//	Fill
			if (this.fillStyle === "radialFill")
			{
				ctx.fillStyle = color.toString();
				ctx.beginPath();
				ctx.moveTo(cx, cy);
				ctx.arc(cx, cy, r, startAngle, endAngle, false);
				ctx.closePath();
				ctx.fill();
			}
			else if (this.fillStyle === "radialStroke")
			{
				ctx.strokeStyle = color.toString();
				ctx.lineWidth = this.lineWidth;
				ctx.beginPath();
				ctx.arc(cx, cy, r, startAngle, endAngle, false);
				ctx.stroke();
			}
			else
			{
				rat.console.log("rat.ui.fillBar does not support fillStyle " + this.fillStyle + " with shape " + this.fillShape);
			}

			//	frame
			ctx.strokeStyle = this.frameColor.toString();
			ctx.lineWidth = 1;
			ctx.beginPath();
			ctx.arc(cx, cy, r, startAngle, endAngle, false);
			ctx.stroke();
		}
		else
		{
			ctx.translate(-this.center.x, -this.center.y);
			//	BG
			ctx.fillStyle = this.backColor.toString();
			ctx.fillRect(0, 0, w, h);

			if (this.fillStyle === "radialFill")
			{
				ctx.beginPath();
				ctx.rect(0, 0, w, h);
				ctx.clip();

				if (w > h)
					r = w;
				else
					r = h;
				r = r * 2;
				ctx.beginPath();
				ctx.moveTo(cx, cy);
				ctx.arc(cx, cy, r, startAngle, endAngle, false);
				ctx.closePath();
				//	BG
				ctx.fillStyle = color.toString();
				ctx.fill();
			}
			else if (this.fillStyle === "radialStroke")
			{
				rat.console.log("rat.ui.fillBar does not support fillStyle " + this.fillStyle + " with shape " + this.fillShape);
			}
			else if (this.fillStyle === "fill")
			{
				ctx.fillStyle = color.toString();
				ctx.fillRect( 0, 0, w*this.percent, h );
			}
			else
			{
				//	Stroke
				ctx.strokeStyle = color.toString();
				ctx.lineWidth = this.lineWidth;
				ctx.strokeRect( 0, 0, w * this.percent, h );
			}

			//	Frame
			ctx.strokeStyle = this.frameColor.toString();
			ctx.lineWidth = 1;
			ctx.strokeRect(0, 0, this.getWidth(), this.getHeight());
		}

		ctx.restore();
	};

	// Support for creation from data
	//
	//colors: {
	// background: {r, g, b, a},
	// body: {r, g, b, a},
	// bodyMin: {r, g, b, a},
	// bodyMax: {r, g, b, a},
	// frame: {r, g, b, a}
	//},
	//}
	//
	rat.ui.FillBar.setupFromData = function (pane, data, parentBounds)
	{
		data.colors = data.colors || {};
		rat.ui.data.callParentSetupFromData(rat.ui.FillBar, pane, data, parentBounds);
		var background = new rat.graphics.Color(data.colors.background || rat.graphics.black);
		var bodyMin = new rat.graphics.Color(data.colors.minBody || data.colors.body || pane.color || rat.graphics.red);
		var bodyMax = new rat.graphics.Color(data.colors.maxBody || data.colors.body || pane.color || rat.graphics.green);
		var frameColor = new rat.graphics.Color(data.colors.frame || (data.frame ? data.frame.color : void 0) || rat.graphics.blue);
		pane.setColors(background, {min:bodyMin, max:bodyMax}, frameColor);
	};
});
//--------------------------------------------------------------------------------------------------
//
//	SpiderChart ui element
//
//	TODO:  Offscreen and Dirty support
//
rat.modules.add( "rat.ui.r_ui_spiderchart",
[
	{name: "rat.ui.r_ui", processBefore: true },
	{name: "rat.utils.r_utils", processBefore: true },
	
	"rat.math.r_math",
	"rat.graphics.r_graphics",
	"rat.debug.r_console",
], 
function(rat)
{
	/**
	 * @constructor
	 * @extends rat.ui.Element
	*/
	rat.ui.SpiderChart = function ()
	{
		rat.ui.SpiderChart.prototype.parentConstructor.call(this); //	default init
		this.setTracksMouse(false);	//	no mouse tracking, highlight, tooltip, etc. including subelements.
	};
	rat.utils.inheritClassFrom(rat.ui.SpiderChart, rat.ui.Element);
	rat.ui.SpiderChart.prototype.elementType = 'spiderChart';

	rat.ui.SpiderChart.START_ANGLE = -rat.math.PI / 2;	//	start first axis straight up

	rat.ui.SpiderChart.prototype.setData = function (data)
	{
		this.data = data;

		//	copy?
		//	fill in a bunch of defaults

		if(typeof this.data.normalizeScale === 'undefined')
			this.data.normalizeScale = 1;

		//	normalizing?
		var biggest = 1;
		for(var setIndex = 0; setIndex < this.data.sets.length; setIndex++)
		{
			var set = this.data.sets[setIndex];
			for(var i = 0; i < set.points.length; i++)
			{
				if(set.points[i] > biggest)
					biggest = set.points[i];
			}
		}
		this.data.normalizeTarget = biggest;

		//	todo: if drawaxes.scale doesn't exist, default to normalizescale above
	};

	var debugtest = true;
	var lastSpider = null;

	rat.ui.SpiderChart.prototype.drawSelf = function ()
	{
		lastSpider = this;

		var ctx = rat.graphics.getContext();
		ctx.fillStyle = "#AAEEFF";	//	todo: let client specify this color
		ctx.fillRect(0, 0, this.size.x, this.size.y);

		var axes = this.data.sets[0].points.length;
		var angleGap = rat.math.PI * 2 / axes;
		var i, angle, dx, dy, v;
		//	start centered
		rat.graphics.save();
		//	clip?
		ctx.translate(this.size.x / 2, this.size.y / 2);

		//	draw axes
		if(this.data.drawAxes.color)
		{
			angle = rat.ui.SpiderChart.START_ANGLE;
			for(i = 0; i < axes; i++)
			{
				v = this.data.drawAxes.scale * this.size.x / 2;

				dx = v * rat.math.cos(angle);
				dy = v * rat.math.sin(angle);

				ctx.beginPath();
				ctx.moveTo(0, 0);
				ctx.lineTo(dx, dy);
				ctx.strokeStyle = this.data.drawAxes.color.toString();
				ctx.stroke();

				angle += angleGap;
			}
		}
		if(this.data.drawAxes.hashColor)
		{
			var hashCount = this.data.drawAxes.hashCount;
			for(var hashIndex = 0; hashIndex < hashCount; hashIndex++)
			{
				angle = rat.ui.SpiderChart.START_ANGLE;
				ctx.beginPath();

				for(i = 0; i < axes; i++)
				{
					//	use normalizescale here, not axes scale, so we match data when it's drawn!
					v = (hashIndex + 1) / hashCount * this.data.normalizeScale * this.size.x / 2;

					dx = v * rat.math.cos(angle);
					dy = v * rat.math.sin(angle);

					if(i === 0)
						ctx.moveTo(dx, dy);
					else
						ctx.lineTo(dx, dy);

					angle += angleGap;
				}

				ctx.closePath();
				ctx.strokeStyle = this.data.drawAxes.hashColor.toString();
				ctx.stroke();

			}
		}
		
		//	draw chart
		for(var setIndex = 0; setIndex < this.data.sets.length; setIndex++)
		{
			var set = this.data.sets[setIndex];
			if(set.fillColor)
				ctx.fillStyle = set.fillColor.toString();
			if(set.strokeColor)
				ctx.strokeStyle = set.strokeColor.toString();
			ctx.lineWidth = set.strokeWidth;	//	todo: default above

			angle = rat.ui.SpiderChart.START_ANGLE;
			ctx.beginPath();
			
			for(i = 0; i < set.points.length; i++)
			{
				v = set.points[i];
				if(this.data.normalize)
					v = v / this.data.normalizeTarget * this.data.normalizeScale * this.size.x / 2;

				var x = v * rat.math.cos(angle);
				var y = v * rat.math.sin(angle);
				if(debugtest)
				{
					rat.console.log(". " + set.points[i] + "(" + v + ") : " + x + ", " + y);
					debugtest = false;
				}

				if(i === 0)
					ctx.moveTo(x, y);
				else
					ctx.lineTo(x, y);

				angle += angleGap;
			}
			ctx.closePath();
			if(set.fillColor)
				ctx.fill();
			if(set.strokeColor)
				ctx.stroke();
		}
		rat.graphics.restore();

	};
});
//--------------------------------------------------------------------------------------------------
//	treeview
//

/*

	Very rough initial tree view.  Maybe useful as a debugging or toolset element.
	
	Could be way more complicated than this one day.
	
	Having some trouble figuring out if we want subelements that know they're in the input map...
	
	Scrolling:
		We're just going to act like we draw our whole tree as it's currently set up to draw.
		If you need this to be in a clipped scrolling space, put it inside a standard scrollview.

	TODO:
		* support totally noninteractive version (but is that the same as simply disabling it?)
		* callback for tree organization change
		* function to query x/y position and size for a given node (by name?), for custom drawing on top of that.
		
		* support NOT displaying root, optionally.
			(it still needs an entry in the tree, and a _treeView structure, etc., just needs to not be added to the visual list,
			not have a treeIndex, etc.)
*/

rat.modules.add( "rat.ui.r_ui_treeview",
[
	{name: "rat.ui.r_ui", processBefore: true },
	
	//"rat.graphics.r_ui_textbox",
	"rat.utils.r_utils",
	"rat.graphics.r_graphics",
	"rat.ui.r_ui_data",
], 
function(rat)
{
	/**
	 * @constructor
	 * @extends rat.ui.Element
	*/
	rat.ui.TreeView = function(parentView)
	{
		rat.ui.TreeView.prototype.parentConstructor.call(this, parentView); //	default init
		
		this.setTracksMouse(true);	//	we do want mouse tracking, highlighting, etc.
		this.setTargetable(true);	//	we're targetable for key input.  Scary...
		
		//	TODO: refactor these frame and selection "look" concepts, which are the same as edittext
		this.editBodyLook = {		//	("fill" look)
			normalColor : new rat.graphics.Color(20, 20, 20),
		};
		this.editFrameLook = {
			normalColor : new rat.graphics.Color(180, 180, 180),
			highlightColor : new rat.graphics.Color(255, 255, 180),
			targetColor : new rat.graphics.Color(255, 255, 255),
			lineWidth : 2,
		};
		this.editSelectionLook = {
			normalColor : new rat.graphics.Color(200, 200, 180, 0.2),	//	selection when inactive - very light
			//activeColor : new rat.graphics.Color(255, 255, 180, 0.8),
		};
		
		this.nextNodeID = 1;		//	unique ID that doesn't change even if things are moved around
		this.setFont("arial", 13, "");
	};
	rat.utils.inheritClassFrom(rat.ui.TreeView, rat.ui.Element);
	rat.ui.TreeView.prototype.elementType = 'treeView';
	
	//	these flag changes need to set me dirty, because they change my look!
	//	see rat.ui module, comments above flagsThatDirtyMe variable,
	//	and rat.ui.Element.prototype.checkFlagsChanged function.
	//	This is similar to buttons...
	rat.ui.TreeView.prototype.flagsThatDirtyMe = 
			rat.ui.Element.highlightedFlag |
			rat.ui.Element.enabledFlag;

	//	set up font
	//	todo: refactor this font code and other similar code (e.g. textbox) to a single font tracking object in rat.
	rat.ui.TreeView.prototype.setFont = function (font, size, style)
	{
		this.font = font;
		if (size)
		{
			this.fontSize = size;
			this.lineHeight = size;	//	may also be set separately later
		}
		if (style)
			this.fontStyle = style;
		this.fontDescriptor = ("" + this.fontStyle + " " + this.fontSize + "px " + this.font).trim();
	};
	
	rat.ui.TreeView.prototype.setLineHeight = function (size)
	{
		this.lineHeight = size;
	};
	
	//	set tree values
	//	We expect this is a single object with a "children" list, each of which is an object with children, etc., like this:
	//	var thing = { label: "topthing", children: [
	//		thing1 : { label : "bob"},
	//		thing2 : { label : "frank"}, children : [
	//				... (etc)
	//		]},
	//	]};
	rat.ui.TreeView.prototype.setTreeValues = function(values)
	{
		var treeView = this;
		this.tree = values;	//	root of tree is the main object passed in
		
		this.removeAllSubElements();
		
		//	build content
		var lineHeight = this.lineHeight;
		var ySpace = 4;
		//var fontHeight = 13;
		var xIndent = 30;
		
		var treeIndex = 0;	//	simple vertical index in list, ignoring parenting and depth

		function addLine(entry, x, y)
		{
			//	for each item we add, we're going to give it a set of extra treeView specific properties.
			if (!entry._treeView)
				entry._treeView = {};
			
			//var elem = rat.ui.makeCheapButton(null, new rat.graphics.Color(180, 180, 180));
			
			var colorStates = [];
			colorStates[0] = {
				flags : rat.ui.Element.enabledFlag, //	normal state
				color : new rat.graphics.Color(0, 0, 0, 0.0),
				frameColor : new rat.graphics.Color(0, 0, 0, 0.0),
				textColor : new rat.graphics.Color(180, 180, 180),
			};
			colorStates[1] = {
				flags : rat.ui.Element.enabledFlag | rat.ui.Element.highlightedFlag, //	highlight state
				color : new rat.graphics.Color(80, 80, 80),
				frameColor : new rat.graphics.Color(0, 0, 0, 0.0),
				textColor : new rat.graphics.Color(180, 180, 180),
			};
			colorStates[2] = {
				flags : rat.ui.Element.enabledFlag | rat.ui.Element.toggledFlag, //	toggled (selected) state
				color : new rat.graphics.Color(40, 40, 180),
				frameColor : new rat.graphics.Color(0, 0, 0, 0.0),
				textColor : new rat.graphics.Color(180, 180, 180),
			};
			
			var elem = rat.ui.makeCheapButtonWithColors(null, colorStates);
			elem.setToggles(true);	//	we use toggle as "selection"
			
			//var tbox = new rat.ui.TextBox(entry.label);
			//tbox.setFont("arial");
			//tbox.setFontSize(fontHeight);
			elem.setFont(treeView.font, treeView.fontSize, treeView.fontStyle);
			elem.setTextValue(entry.label);
			elem.getTextBox().setAlign(rat.ui.TextBox.alignLeft);
			elem.setPos(x, y);
			elem.setSize(treeView.size.x - x - 100, lineHeight);	//	hack - don't make them full width, so I can scroll...?
			//tbox.setAlign(rat.ui.TextBox.alignLeft);
			//tbox.setFrame(1, rat.graphics.yellow);
			//tbox.setColor(new rat.graphics.Color(180, 180, 180));
			elem.setCallback(function(thisElem, userData)
			{
				treeView.selectNodeByID(thisElem.treeNode._treeView.treeNodeID);	//	mostly to unselect the rest.
			}, null);
			
			treeView.appendSubElement(elem);
			
			//	and mark each entry (in the original data!) with a unique ID for later manipulation,
			//	IF it doesn't already have one.
			//	and make some convenient cross-references...
			if (!entry._treeView.treeNodeID)
				entry._treeView.treeNodeID = treeView.nextNodeID++;
			
			entry._treeView.treeIndex = treeIndex++;	//	tree index gets reset any time we're building this list - it's a simple vertical index
			
			entry._treeView.elem = elem;
			elem.treeNode = entry;
			
			return lineHeight;
		}
		
		function addSubTree(parent, list, x, y)
		{
			var startY = y;
			for (var i = 0; i < list.length; i++)
			{
				var entry = list[i];
				
				addLine(entry, x, y);
				entry._treeView.parentNode = parent;
				
				y += lineHeight + ySpace;
				
				if (entry.children)
				{
					var subHeight = addSubTree(entry, entry.children, x + xIndent, y);
					y += subHeight;
				}
			}
			return y - startY;
		};
		
		var height = 0;
		var x = 5;
		var y = 5;
		if (this.tree)
		{
			height = addLine(this.tree, x, y)
			y += height + ySpace;
			x += xIndent;
			if (this.tree.children)
				height += addSubTree(this.tree, this.tree.children, x, y);
		}
		
		this.elemCount = treeIndex;
		
		this.setContentSize(this.size.x, height + ySpace);
		
		this.setDirty(true);
	};
	
	//	return full tree of values (return top-level object, which has children)
	rat.ui.TreeView.prototype.getTreeValues = function()
	{
		return this.tree;
	};
	
	
	rat.ui.TreeView.prototype.removeChildFrom = function(nodeParent, nodeChild)
	{
		if (nodeParent.children)
		{
			for (var i = 0; i < nodeParent.children.length; i++)
			{
				if (nodeParent.children[i]._treeView.treeNodeID === nodeChild._treeView.treeNodeID)
				{
					nodeParent.children.splice(i, 1);
					nodeChild._treeView.parentNode = null;
					//	also remove ui element, if any
					//	this is pretty questionable, since all the other UI elements need to get visually moved, too!
					//	need another separate function that does that.
					//	For now, we actually depend on the whole visual tree being rebuilt elsewhere after this.
					//if (nodeParent._treeView.elem && nodeChild._treeView.elem)
					//{
					//	nodeParent._treeView.elem.removeSubElement(nodeChild._treeView.elem);
					//}
					
					if (this.removeChildCallback)
						this.removeChildCallback(this, this.tree, nodeParent, nodeChild);
					
					return true;
				}
			}
		}
		
		return false;
	};
	
	//	add this node to a parent's children list, with optional requested index.
	rat.ui.TreeView.prototype.addChildTo = function(nodeParent, nodeChild, atIndex)
	{
		if (!nodeParent.children)
			nodeParent.children = [];
		if (atIndex === void 0)
			atIndex = nodeParent.children.length;
		
		nodeParent.children.splice(atIndex, 0, nodeChild);
		nodeChild._treeView.parentNode = nodeParent;
		
		if (this.addChildCallback)
			this.addChildCallback(this, this.tree, nodeParent, nodeChild, atIndex);
		
		//	add visual element.  This is wrong - For now, we actually depend on the whole visual tree being rebuilt elsewhere after this.
		//if (nodeParent._treeView.elem && nodeChild._treeView.elem)
		//	nodeParent._treeView.elem.appendSubElement(nodeChild._treeView.elem);
		return true;
	};
	
	//	return the index of this child in this parent, or -1 if not found
	rat.ui.TreeView.prototype.getChildIndex = function(nodeParent, nodeChild)
	{
		if (nodeParent.children)
		{
			for (var i = 0; i < nodeParent.children.length; i++)
			{
				if (nodeParent.children[i]._treeView.treeNodeID === nodeChild._treeView.treeNodeID)
					return i;
			}
		}
		return -1;
	};
	
	rat.ui.TreeView.prototype.isDescendentOf = function(nodeParent, checkNode)
	{
		do {
			var parent = checkNode._treeView.parentNode;
			if (parent === nodeParent)
				return true;
			checkNode = parent;
		} while (checkNode);
		
		return false;
	};

	//	Generic utility to apply some function to the whole tree.
	//	The function takes a single node pointer and userdata argument
	//	If the function returns non-void, it's done, and we should stop recursing, and return that value.
	rat.ui.TreeView.prototype.applyToTree = function(func, userData, node)
	{
		if (!node)
			node = this.tree;
		
		if (!node)	//	nothing to apply to
			return;
		
		var res = func(node, userData);
		if (res !== void 0)
			return res;
		
		if (node.children)
		{
			for (var i = 0; i < node.children.length; i++)
			{
				var entry = node.children[i];
				var res = this.applyToTree(func, userData, entry);
				if (res !== void 0)
					return res;
			}
		}
		return void 0;
	};
	
	//	select (toggle?) the node with this id, if there is one.
	//	unselect the rest.
	rat.ui.TreeView.prototype.selectNodeByID = function(inID)
	{
		var selNode = null;
		this.applyToTree(function(node, id)
		{
			if (node._treeView.elem)
			{
				if (node._treeView.treeNodeID === id)
				{
					selNode = node;
					node._treeView.elem.setToggled(true);
				}
				else
					node._treeView.elem.setToggled(false);
			}
			
			return void 0;
		}, inID);
		
		if (this.selectionCallback)
			this.selectionCallback(this, this.tree, [selNode]);
	};
	
	//	select (toggle?) the node with this tree index, if there is one.
	//	unselect the rest.
	rat.ui.TreeView.prototype.selectNodeByIndex = function(inIndex)
	{
		var node = this.getNodeByIndex(inIndex);
		if (node)
			this.selectNodeByID(node._treeView.treeNodeID);
	};
	rat.ui.TreeView.prototype.selectNode = function(node)
	{
		if (!node || !node._treeView)	//	invalid, so unselect everything
			this.selectNodeByID(-99999);	//	hack.  todo: we need a "clear selection" function...
		else
			this.selectNodeByID(node._treeView.treeNodeID);
	};
	
	//	return the node with this tree index, if there is one
	rat.ui.TreeView.prototype.getNodeByIndex = function(inIndex)
	{
		return this.applyToTree(function(node, index)
		{
			if (node._treeView.treeIndex === index)
				return node;
			else
				return (void 0);
		}, inIndex);
	};
	
	//	return data tree node matching whatever's selected right now
	rat.ui.TreeView.prototype.getSelectedNode = function()
	{
		return this.applyToTree(function(node, userData)
		{
			if (node._treeView.elem && node._treeView.elem.isToggled())
			{
				return node;
			}
		});
	};
	
	//	given a particular node, return its parent node, if there is one.
	rat.ui.TreeView.prototype.getNodeParent = function(targetNode)
	{
		if (!targetNode)
			return null;
		
		function search(node)
		{
			for (var i = 0; i < node.children.length; i++)
			{
				var entry = node.children[i];
				if (entry._treeView.treeNodeID === targetNode._treeView.treeNodeID)
					return node;
				
				if (entry.children)
				{
					var res = search(entry);
					if (res)
						return res;
				}
			}
			return null;
		}
		return search(this.tree);
	};
	
	//	reorganize this node's position
	//	return true if tree changed.
	rat.ui.TreeView.prototype.moveNode = function(selectedNode, dirKey)
	{
		if (!selectedNode)
			return false;
		
		var treeChanged = false;
		var nodeParent = this.getNodeParent(selectedNode);
		var gparent = null;
		if (nodeParent)
			gparent = this.getNodeParent(nodeParent);
		
		if (dirKey === rat.keys.leftArrow)
		{
			//	move out of my parent
			if (gparent)
			{
				var parIndex = this.getChildIndex(gparent, nodeParent);
				
				this.removeChildFrom(nodeParent, selectedNode);
				this.addChildTo(gparent, selectedNode, parIndex);	//	insert right before parent slot
				treeChanged = true;
			}
		} else if (dirKey === rat.keys.rightArrow)
		{
			//	move inside the next element in list
			var index = selectedNode._treeView.treeIndex;
			var targetNode;
			//	but skip any node that's my descendent.
			do {
				targetNode = this.getNodeByIndex(++index);
			} while (targetNode && this.isDescendentOf(selectedNode, targetNode));
			if (nodeParent && targetNode)
			{
				this.removeChildFrom(nodeParent, selectedNode);
				this.addChildTo(targetNode, selectedNode, 0);	//	insert at start
				treeChanged = true;
			}
		} else if (dirKey === rat.keys.upArrow)
		{
			//	Move up visually.
			//	Trickier.  If I'm not the first in a child list, then reorder me in my parent.
			//	If I'm the first in a child list, then remove me from parent and put me before parent in gparent's children.
			var sibIndex = this.getChildIndex(nodeParent, selectedNode);
			if (sibIndex > 0)
			{
				this.removeChildFrom(nodeParent, selectedNode);
				this.addChildTo(nodeParent, selectedNode, sibIndex-1);
				treeChanged = true;
			}
//			else if (gparent) {
				//get index of my parent in gparent
				//
				//this.removeChildFrom(nodeParent, selectedNode);
				//	Actually, let's not do this for now...
				//	let's have arrows just work inside parnet.
				//	you have to hit left/right arrow to move to another level
//			}
		} else if (dirKey === rat.keys.downArrow)
		{
			//	Move down visually.
			//	Trickier.  If I'm not the last in a child list, then reorder me in my parent.
			//	If I'm the last in a child list, then remove me from parent and put me after parent in gparent's children.
			var sibIndex = this.getChildIndex(nodeParent, selectedNode);
			if (sibIndex < nodeParent.children.length-1)
			{
				this.removeChildFrom(nodeParent, selectedNode);
				this.addChildTo(nodeParent, selectedNode, sibIndex+1);
				treeChanged = true;
			}
//			else if (gparent) {
//				//	todo, but maybe.  see above.
//			}
		}
			
		if (treeChanged)
		{
			//	this is a heavy version - completely replace all contents.
			//	Lots of stuff like selection will be broken in that case.  :(
			//	including input order, tab order, etc.
			//	It'll be a lot more work, but can we please just move ui elements around explicitly,
			//	instead of rebuilding?
			this.setTreeValues(this.tree);
			
			//	reselect the one that was selected before
			this.selectNodeByID(selectedNode._treeView.treeNodeID);
			
			if (this.changeCallback)
				this.changeCallback(this, this.tree, '');
			
			return true;
		}
		return false;
	};
	
	//	draw frame to help indicate state?
	//	(e.g. we are targeted, keystrokes will go to us)
	rat.ui.TreeView.prototype.drawFrame = function(ctx)
	{
		/*
		var frameLook = this.editFrameLook;
		ctx.lineWidth = frameLook.lineWidth;
		
		var outset = 0;
		
		var targeted = (this.flags & rat.ui.Element.targetedFlag) !== 0;
		var highlighted = (this.flags & rat.ui.Element.enabledFlag) && (this.flags & rat.ui.Element.highlightedFlag);
		
		if (highlighted)
			ctx.strokeStyle = frameLook.highlightColor.toString();
		else if (targeted)
			ctx.strokeStyle = frameLook.targetColor.toString();
		else
			ctx.strokeStyle = frameLook.normalColor.toString();
		
		//	still feeling this out.  Feel free to change it.
		if (highlighted || targeted)
		{
			ctx.lineWidth = frameLook.lineWidth + 1;
			outset = 1;
		}
		
		//	draw frame		
		ctx.strokeRect(-this.center.x - outset, -this.center.y - outset,
					this.size.x + 2 * outset, this.size.y + 2 * outset);
		*/
	};
	
	//	override draw to draw carat and selection, etc.
	rat.ui.TreeView.prototype.drawSelf = function()
	{
		var ctx = rat.graphics.getContext();
		
		//	draw a background		
		//ctx.fillStyle = this.editBodyLook.normalColor.toString();
		//ctx.fillRect(-this.center.x, -this.center.y, this.size.x, this.size.y);
		
		//	draw a frame to indicate state?
		//	maybe more appropriate for a scrollview containing us?
		//this.drawFrame(ctx);
		
	};
	
	rat.ui.TreeView.prototype.updateSelf = function(dt)
	{
		
	};
	
	//	handle keys
	//	This is a hacky way to control hierarchy.
	//	Long-term, we'll certainly support mouse control,
	//	and this stuff could be cleaned up and optional.
	rat.ui.TreeView.prototype.keyDown = function(ratEvent)
	{
		var which = ratEvent.which;
		
		//if (ratEvent.which === rat.keys.esc)
		//{
		//	//return true;
		//}
		//else if (ratEvent.which === rat.keys.enter)
		//{
		//	//return false;
		//}
		
		var selectedNode = this.getSelectedNode();
		
		//	non-meta key means move selection.
		if (!ratEvent.sysEvent.altKey && selectedNode)
		{
			var index = selectedNode._treeView.treeIndex;

			if (ratEvent.which === rat.keys.upArrow && index > 0)
			{
				this.selectNodeByIndex(index-1);
				return true;
			} else if (ratEvent.which === rat.keys.downArrow && index < this.elemCount-1) {
				this.selectNodeByIndex(index+1);
				return true;
			}
			//	todo: return true even if they hit up/down on top/bottom element?
			//	right now, it'll move out of the tree if there's an input map...
			//	I could see either reaction being legit.
		}
		
		//	meta key means move this node around, using various (experimental) rules
		//	report any meta key arrow as handled, since normally we handle them,
		//	and we don't want to sometimes return handled and sometimes not.
		var wasMetaKeyArrow = false;
		
		if (rat.input.keyToDirection(ratEvent.which) && ratEvent.sysEvent.altKey)
		{
			this.moveNode(selectedNode, ratEvent.which);
			wasMetaKeyArrow = true;
		}
		
		if (wasMetaKeyArrow)
			return true;
		
		return false;
	};
	
	//	mouse down - start tracking some stuff?
	rat.ui.TreeView.prototype.mouseDown = function(pos, ratEvent)
	{
		//	call inherited to get correct flags cleared/set
		return rat.ui.TreeView.prototype.parentPrototype.mouseDown.call(this, pos, ratEvent);
		
		//	also, we should become target now.  Is that already handled?
		
		//return true;
	};
	
	rat.ui.TreeView.prototype.mouseUp = function(pos, ratEvent)
	{
		//	call inherited to get correct flags cleared/set
		return rat.ui.TreeView.prototype.parentPrototype.mouseUp.call(this, pos, ratEvent);
		
		//return false;
	};
	
	//	track mouse movement for drags
	rat.ui.TreeView.prototype.mouseMove = function(pos, ratEvent)
	{
		//	note that we don't care if cursor is really in our bounds.
		//	It's OK to drag outside our space, as long as it was all
		//	from a click that started in our space.
		/*
		if (this.trackingClick)
		{
			return true;
		}
		return false;
		*/
	};
	
	//	for keyboard/controller support, also go active on trigger (action button) if not already active?
	rat.ui.TreeView.prototype.trigger = function()
	{
		return rat.ui.TreeView.prototype.parentPrototype.trigger.call(this); //	inherited trigger
	};
	
	// Support for creation from data
	rat.ui.TreeView.setupFromData = function(pane, data, parentBounds)
	{
		rat.ui.data.callParentSetupFromData( rat.ui.TreeView, pane, data, parentBounds );
	};
});
//--------------------------------------------------------------------------------------------------
//
//	Firebase utilities and support classes
//
//	This is for handling common stuff we do with firebase, like track connection status,
//	and start at a project level of an often-used top-level firebase server.
//
//	NOTE:  This (and any new firebase-related code in rat, hopefully)
//		uses the 3.x API for firebase.
//		It's possible we could retrofit this to the old API if there's a problem.
//		(another advantage to having this layer here)
//
//	Also note that rat doesn't require or depend on firebase for normal usage.  The various
//		firebase-specific modules are all optional, and even if included in builds of rat,
//		they don't immediately try to interact with firebase unless actually initialized and used.
//
//	Connection:
//		tracks a current connection to a firebase database, including tracking connected/disconnected state.
//		var myConnection = new rat.live.firebase.Connection('rtest');
//
//	We assume somewhere in your app you have previously called firebase.initializeApp() (see firebase docs)
//
//	Useful Firebase debugging notes:
//		http://stackoverflow.com/questions/39076472/performance-of-listening-to-the-same-firebase-ref-multiple-times
//		Also, firebase.database().goOffline and goOnline are super useful for testing!
//
//	TODO:
//		* Once a firebase server has been specified (projectRoot and firebase object)
//			then assume that for all future firebase objects, for ease in creating several of these in one project.

rat.modules.add("rat.live.r_firebase",
[
	{ name: "rat.os.r_system", processBefore: true },
	{ name: "rat.os.r_events", processBefore: true },
	"rat.debug.r_console",
],
function (rat) {
	if (!rat.live)
	{
		/**  Namespace for general group of rat online/live/network features
		    @namespace
 */
		rat.live = {};
	}
	/** 
	    Namespace for firebase support functionality
	    @namespace
	   
 */
	rat.live.firebase = {};			//	rat firebase namespace
	
	//	hmm... this doesn't seem to work in jsdoc, so I'm using long names below.
	/**  @alias rat.live.firebase
 */
	var rlf = rat.live.firebase;	//	local var for my convenience...
	
	//	firebase server connection class.
	//	We may have more than one of these instantiated in any given game, 
	//	e.g. one for matchmaking, another for the game, another for telemetry, etc.
	//	The functionality is collected here since we kept using the same code...
	
	/**
	 * @constructor
	 * @param {string=} projectRoot project root path, if needed, e.g. "rtest/mygame" or something
	 * @param {object=} useFirebase firebase instance ("app") to use.  Optional!
	 */
	rat.live.firebase.Connection = function(projectRoot, useFirebase)
	{
		if (!useFirebase)
			useFirebase = firebase;	//	use default
		
		var db = useFirebase.database();
		this.db = db;
		//	all my normal references are from now on below this root
		this.baseRef = db.ref(projectRoot);
		
		this.state = 'init';
		this.connected = false;
		
		//this.projectRoot = projectRoot;	//	not needed?
		
		if (!("firebase" in window))	//	no firebase installed?
		{
			rat.console.log("!no firebase!");
			return;
		}

		rat.console.log("firebase connection init");

		//	For Win8, force web sockets...  See http://stackoverflow.com/questions/19828632/is-it-possible-to-use-firebase-with-a-windows-8-app
		//	(this wasn't a problem for a while, but suddenly started being a problem)
		//	This can also be a problem for other platforms, so I'm leaving it here for a possible troubleshooting reference...
		//if (rat.system.has.windows8)
		//{
		//	firebase.INTERNAL.forceWebSockets();
		//}

		//	Notes on connection state...
		//	Firebase references DO get created even if we start up offline.
		//	The code below tracks explicit connection state.
		
		//	If we start up offline, we get a not-connected message, and our attempt to read stats never succeeds.
		//	Note that firebase also does a series of attempts to connect.  This doesn't seem to end, but it slows down?
		//	If we eventually reconnect, we'll suddenly get online message, AND initial read attempt will finally succeed.
		
		//	If we start up online, we get a not-connected message then a connected message.
		//	If we then disconnect on the fly, we get a not-connected message, but note that state = ok, and we have data, in that case.
		//	And we can make local progress, but it's not saved if the user doesn't reconnect.
		
		//this.connectedRef = new Firebase(this.FIREBASE_CONNECTED_PATH);
		this.connectedRef = db.ref(".info/connected");
		var con = this;
		this.connectCallback = this.connectedRef.on('value', function(snap) {
			if (con.state === 'init')
				con.state = 'ok';	//	I read something successfully
			if (snap.val() === true) {
				rat.console.log("++ rat.firebase connected");
				con.connected = true;
				if (con.connectFunction)
					con.connectFunction(con);
					
			} else {
				rat.console.log("-- rat.firebase disconnected");
				con.connected = false;
				if (con.disconnectFunction)
					con.disconnectFunction(con);
			}
		});
	};
	
	/** 	set disconnect callback function
 */
	rat.live.firebase.Connection.prototype.onDisconnect = function(f)
	{
		this.disconnectFunction = f;
	};
	
	/** 	set connect callback function
 */
	rat.live.firebase.Connection.prototype.onConnect = function(f)
	{
		this.connectFunction = f;
	};
	
	/** 	get base firebase reference object
 */
	rat.live.firebase.Connection.prototype.ref = function(path)
	{
		return this.baseRef.child(path);
	};
	
	/** 	close this connection, disconnect callbacks
 */
	rat.live.firebase.Connection.prototype.close = function()
	{
		if (this.connectedRef && this.connectCallback)
		{
			//	remove specifically my own callback - there may be several of us active at once.
			this.connectedRef.off('value', this.connectCallback);
			this.connectCallback = null;
			this.connectedRef = null;
		}
		this.baseRef = null;
	};
	
	/** 	Is the system up and running correctly?  Did we finish init and get some data at some point?
 */
	rat.live.firebase.Connection.prototype.isReady = function()
	{
		if (this.state != 'ok')
			return false;
		return true;
	};
	
	/** 	Is the system connected right now?  (this can go up and down)
 */
	rat.live.firebase.Connection.prototype.isConnected = function()
	{
		return this.connected;
	};
	
});
//--------------------------------------------------------------------------------------------------
//
//	Simple data sync service - can be used to make a simple networked game...
//
/**

The Basic Idea:
	Tell this module what data you want synced between players, and it'll take care of it.

Overview of features:
		* Player list management
		* Syncing of data between players, including lists, without much involvement from game
		* Event Queue (useful for game events from host)
		* Request Queue (useful for clients making requests from host)
		* Chat
		* Object ownership (useful for a player to take control of an actor, and then give it up later)

	This is not nearly as robust as it would need to be for a complex game.
	It still has no security support.
	It's useful for simple games, prototypes, etc.
	It's well-suited for observing a game, or games that have a lot to observe, like simulations.
	Probably not well-suited for games where immediate action is crucial (though I have no reason to think there's extra lag introduced)
	Not well suited for complicated deep data, because we mostly just deal with a single layer of "things to sync", right now.
	
DETAILS:

	Philosophy:
		Never write unless something changed.
		Don't expose Firebase itself - could switch out to a different tech.
		Try to handle things ourselves - once we're set up, bug the game only as much as it wants.
		Don't try to be more than we are - this is a simple model, and just intended for games.

	Player List
		We maintain a player list here, handle assigning network IDs (_syncID), etc.
		The player list is a hash by id, not an array.
		We just keep a local copy and expect the game logic to access that when desired,
			and it's expected that the game may have its own copy of a player list and resolve
			them as needed, e.g. if they fill in with AI or something.
		We notify when player list changes.
		The player list can be augmented on the fly with game-specific properties  - call updatePlayer() to add values.
			(_syncID is our own value, and we care about "isHost", but I think nothing else)

	Synced Data
		call register(), pass in your data reference (which we assume will be active for a while), and some info about what is to be synced.
		We default to syncing nothing until it's marked specifically as needing sync.
		
	Properties
		User specifies which properties to write.  Don't write something unless it's called out.  We want to write only what's needed.
		
		TODO:
			OK, I give up, and I admit it'd be useful to support "all properties" explicitly,
			especially in the case of "props" not being specified when register() is called,
			and no source data being available yet to infer default properties from.
			Maybe do that as a special flag in the map?
		
	Arrays
		For top-level arrays (e.g. the thing registered for syncing is an array), we do this:
			Add each item in the array to a firebase list with unique firebase ids
			Store those unique ids in our data so we can correctly match values up later.
			This allows the game to think of data as an array,
			but lets us assure proper syncing with database including reflecting added/deleted elements
		For arrays deeper inside data, we just read and write them as whole arrays and expect the game can deal with that.
		An array at the top level with an array inside it will not work, though deeper than that, array in array should work.
		
		IDs
			for top-level array entries, they only get added to the server through firebase push,
			so we can always get an ID from that and use that.
			_syncID (firebase id) is set back in original data,
			so we can match update data later.
	
	Reckoning (UNIMPLEMENTED)
		BEST option would be for server to know how client is updating (how?)
		and write the best info for client to guess at position.
			but this implies new data that's not in the source data, like delta values or something.
			maybe just add those in?  Like, a vector value will have "_r_x" and "_r_y" values or something?
			Does that mean this only works for objects, so we have somewhere to put our extra data?
			maybe not the end of the world.
		Reckoning is hard to do in a generic way, so it's not implemented yet.
		Do it yourself for now.  Exclude the data you want updated every frame.  Include the info you need to estimate it.
		
	Infrequent Updates (implemented with 'low' property, but needs work)
		This is probably more generally useful and needed than reckoning, at least at first.
		flag some values as updating "occasionally", maybe with some timer?
		again without the game logic needing to worry about it.  We just set it up, and sync system watches for when to post changes.
		We'll need to be very sure that our local copy (curData) reflects what we know about the online data, not what we know about local data.
		because we still only want to do sparse updates if the data changes!
		
	Partial writes (implemented)
		In simple cases where only one value in a structure changed, why do we write the whole structure?
		Wouldn't it be more optimal to write only the part that we noticed that changed?
		And, this seems pretty critical.  Right now, any little change to one property of an object
		overwrites any other changes the server might be handling...
		See updateObjectIfChanged.
		
		A crucial part of this is using firebase's "update" functionality to do all the changes at once,
		since otherwise we get a lot of little 'value' change callbacks, which are a pain and unoptimal.
		
		Problem:
			Now that we're only writing actual changes,
				the data we only sometimes want to change, like 'reckon' data,
				is not being written.
				
				I'm making big assumptions that update() will only do one write on host and one read on each client.
				One problem is that this means reckon values update at unpredictable times.  :(
				
			See sync_test for some handling of this case.  The key thing is to call markChanged() on reckon data.
	
	Object Ownership
		To facilitate clients temporarily controlling individual objects,
		we have an ownership system.  Clients request ownership and server grants it.
		All client nodes can watch the ownership list.
		See requestOwnership() and releaseOwnership()
		and iOwn() and getOwner()
		
		we occasionally check and clean up any objects whose owners seem to be gone from the player list.
			this is to handle edge cases like owner is host who leaves, or player leaves right when host migration is happening...
			
		Object ownership is implemented in a list separate from objects, so we can cleanly and reliably
		manage ownership without triggering overwrites of object data, and without being overwritten by object changes.

	Host Migration!
	
		If host disconnects, make somebody else host, so there's always a live host.
		
		If host migration is disabled (in config on init), original host is only ever the host,
			and if that player leaves, there will be no host from then on.  Not usually desired, but OK if you want it,
			and probably nice for development purposes when you don't want your host/client state suddenly changing.
		
		Host migration is why we ask you to pass in operation flags for both host and client when registering data.
			So when you switch to/from host, your operations are correct.
		Provide promoteHost and demoteHost callbacks if you want to be notified when your status changes.

		Host migration is triggered by player disconnect.
		When host disconnects, the next player in the list is assumed by everyone to be the next host.
		That player will promote itself.
		
		Host migration also happens if several seconds pass with no known host.
		This also covers the case of a single player hosting a game, disconnecting, and reconnecting to the same game alone,
			Also covers host leaving and another player joining an otherwise empty game (not currently possible, but will be eventually)
		
		[unimplemented:]
		We also host migrate if the supposed host hasn't updated their timestamp in a while.
			In general, it's probably a good idea to occasionally just check if
			the player who claims to be host hasn't updated his timestamp in a while.
			but in that case, we probably need to deal with forcing that guy to let go of host status,
			once the new host is in place.  E.g. if old host wakes up and thinks he's still host,
			he needs to be disabused of that notion right away somehow.  So, handle forced demotions.
		
		We need to be sure that on disconnect/reconnect we get readded to everyone's player list.  Currently, that's the case.
		
		Note:  Host migration and other connect/disconnect behavior can nicely be tested with firebase's goOffline function
			(rat.live.sync.connection.db.goOffline and goOnline)
			these are also mapped to rat console commands "disconnect" and "connect", for convenience.
		
	TODO:
	
		This property system is complicated and I don't like it any more.
			I'm using a single props structure for one array, assumed to be the same for all entries of the array,
			and that's fine, but in practice I want to set and clear flags, and I want to do it per object, not per map entry.
		It's also weird that we can't set flags for an object in the property list if that object needs detailed subflags...
			but maybe that's right?
		We should standardize on a name for this thing, like propScheme or something.
		
		* deep property definitions, particularly in copyProps.  Right now, registered prop descriptions are only one level deep.
		
		* filter out functions in copyProps recursion
			* and test this in sync_test
	
		* clean up code and modularize more
		
		* Chat improvements:
			separate module? useful more generically?
			more than chat - emoticons or something?
			timestamp - send local timezone and local time as well?  Do we care what TZ somebody is in?  Store that in player list?
			
			New chat log ui element
				understands out chat log format here and displays nicely and flexibly,
				handling cropped long text, timestamps, etc.
				remember player colors.
				Actually, probably remember each line as it's reported so we don't need player list long-term to draw.
				support being connected directly to this sync object and handling all the details once set up.
				certainly support offscreen use.
				maybe include edit box?  Maybe that's asking too much...
				
		* shared network debug log - maybe a subset of the event queue, and not passed on to game.
			convenient formatting, include player name/id, etc.  Could help with understanding what network events are happening,
			like promotions and connections and stuff?
			
		* auto-queue event or chat message about disconnection and reconnection when I leave/join?  Is push() supported as an onDisconnect action?  No.
			Maybe have every client do it on disconnect messages?  But their data matches what's on the server...
			also, do I only get one onDisconnect?
			
		* Somehow support the concept of recognizing a returning player, even when (especially when?) there's only one player who briefly disconnected.
			This is a big general idea, and applies in several ways - assigning color, restoring which civilization you picked, which team, etc.
			Think about this.
*/

rat.modules.add("rat.live.r_sync",
[
	{ name: "rat.live.r_firebase", processBefore: true },
	{ name: "rat.os.r_system", processBefore: true },
	"rat.debug.r_console",
],
function (rat) {
	if (!rat.live)
		rat.live = {};
	/** 
	    Namespace for sync functionality
	    @namespace
	   
 */
	rat.live.sync = {
		playerList : {},
		map : {},
	};
	var sync = rat.live.sync;	//	local convenient reference
	
	//	bottleneck for debugging messages
	sync.debugLog = function(message)
	{
		//rat.console.log("SY |" + message);
	}
	
	//	init
	//	project root example 'rtest'
	//	"handler" is an object with some subset of the standard callback functions implemented.
	//		If the functions exist, they get called.
	sync.init = function(config, playerInfo, handler)
	{
		//	process arguments
		sync.config = config || {};
		
		if (sync.config.hostMigration === void 0)	//	default host migration to true
			sync.config.hostMigration = true;
		if (sync.config.hostHeartbeatFrequency === void 0)
			sync.config.hostHeartbeatFrequency = 2;
		
		//	autodelete does not work!
		//if (sync.config.autoDelete === void 0)
		//	sync.config.autoDelete = true;	//	default autodelete

		sync.projectRoot = sync.config.projectRoot || "";
		useFirebase = sync.config.useFirebase;	//	optional, OK to be undefined
		
		//	Internals
		sync.timer = 0;
		
		//	init
		sync.debugLog("sync init " + sync.config.id);
		sync.connection = new rat.live.firebase.Connection(sync.projectRoot, useFirebase);
		//	the above connection creation actually immediately triggers a connection off,
		//	but we haven't registered our callbacks yet, so we can ignore that one.  :)
		
		//	todo: should set these before first connecting,
		//	but keep in mind that we get a disconnect right away and then eventually a connect.
		sync.connection.onDisconnect(function(con){
			console.log("ON DIS");
		});
		sync.connection.onConnect(function(con){
			console.log("ON CON");
			//	reconnect my player.
			//	NOTE: this ends up happening twice - once below when we first add ourselves to the
			//	player list, and again here when we get notification of connecting.
			//	But we really needed the first one to get our own unique player ID from firebase.
			//	And it seems no harm is done.
			sync.connectMyPlayerInfo();
		});
		
		sync.handler = handler;
		
		sync.myPlayerInfo = playerInfo;	//	my local player info
		if (playerInfo.isHost)
			sync.isHost = true;
		
		//	my game root reference...
		sync.mRef = sync.connection.ref('games/' + sync.config.id);
		
		//	Maybe write and read something once, at startup, so we know we're getting data...?
		//	"_info" (gamestats) is a good candidate for that.  We always read and write to that.
		
		//	We don't delete whole games on disconnect
		//	because of host migration and maybe the ability to reconnect automatically to "the last game".
		//	But we write (and maintain over time) a date last accessed.
		
		//	We also store other values in the game's "info" block,
		//	some of which is only written if we're a host.
		var d = new Date();
		//sync.debugLog(d.toUTCString());
		var timeString = d.toUTCString();
		
		//	high level game info of some kind...  when created, etc.?
		//	At least write the last time somebody connected.  That's interesting, right?
		//	later this also holds server update heartbeat
		var infoValues = {
			'lastDate' : d.getTime(),
			'lastDateString' : timeString,
		};
		var statsRef = sync.mRef.child("_info/");
		sync.statsRef = statsRef;
		statsRef.on('value', function(snap) {	//	update our copy of those
			sync.gameStats = snap.val();
		});
		statsRef.update(infoValues);	//	update what we know so far
		
		sync.hookUpPlayerList();
		
		sync.hookUpEventList();
		
		sync.hookUpRequestList();
		
		sync.hookUpChat();
		
		sync.hookUpMatchConfig();
		
		sync.setupOwnershipTracking();
		
		sync.debugLog("sync init done");
		
		//	make easy connection debug commands available in console
		rat.console.registerCommand("disconnect", function (cmd, args)
		{
			sync.connection.db.goOffline();
		});
		rat.console.registerCommand("connect", function (cmd, args)
		{
			sync.connection.db.goOnline();
		}, ["reconnect"]);
		

		//	We'll just return player info for now, since that ID is available now,
		//	but later we might add a callback mechanism if we need initial data we haven't read yet...
		return {
			playerID : sync.myPlayerInfo._syncID,
		};
	};
	
	//	disconnect from sync server
	sync.shutDown = function()
	{
		sync.playersRef.off();
		
		sync.connection.close();
		sync.connection = null;
	};
	
	//	Is the system up and running correctly?  Did we finish init and get some data at some point?
	sync.isReady = function()
	{
		return sync.connection.isReady();
	};
	//	Is the system connected right now?  (this can go up and down)
	sync.isConnected = function()
	{
		return sync.connection.isConnected();
	};
	
	sync.getMatchConfig = function()
	{
		if (sync.config.matchConfig)
			return sync.config.matchConfig;
	};
	
	//	player list management...
	sync.hookUpPlayerList = function()
	{
		sync.playersRef = sync.mRef.child("_players/");

		//	if we're host, reset player list
		if (sync.isHost)
			sync.playersRef.set({});

		//	figure out my own player ref and get a unique ID from firebase
		sync.myPlayerRef = sync.playersRef.push();
		sync.myPlayerInfo._syncID = sync.myPlayerRef.key;	//	grab that ID right now, since it's known.
		//	then write my player info
		sync.connectMyPlayerInfo();
		
		//	listen to player list changes.
		//	Note:  This will actually happen more than once in a row sometimes...
		//	for instance, if the callback we call here happens to further change the player list
		//	(e.g. the host decides to add some properties)
		//	then the list will immediately change again!
		//	So, don't freak out when this is called.
		sync.playersRef.on('value', function(snap) {
			sync.playerList = snap.val();
			if (sync.playerList === null)	//	all players left but my code is still catching up...
				sync.playerList = {};	//	OK to be empty, but don't be null

			//	debug:
			//for (var k in sync.playerList)
			//{
			//	rat.console.log("  p: " + k);
			//}
			
			//	update our local copy of our own player data, in case somebody changed it.
			var myID = sync.myPlayerInfo._syncID;
			if (sync.playerList[myID])	//	as long as it wasn't deleted
				sync.myPlayerInfo = sync.playerList[myID];
			//	fix id which we just overwrote?
			sync.myPlayerInfo._syncID = myID;
			
			//	and tell game handler about this.
			if (sync.handler.playerListChanged)
			{
				sync.handler.playerListChanged(sync.playerList);
			}
			
			//sync.updateAutoDelete();
		});
		
		//	Listen to child_removed events so we know when players leave
		sync.playersRef.on('child_removed', function(snap) {
			//	snap.key is our key
			//	snap.val() is legit, still.
			var val = snap.val();
			
			//	handle host migration
			if (sync.config.hostMigration && val.isHost)	//	designated host left
			{
				if (sync.myPlayerInfo._syncID === snap.key)
				{
					//	this was me.  I guess I'm disconnected.
					sync.demoteHost();
				} else {
					//	find first player in the list who is not host already.
					for (var k in sync.playerList)
					{
						if (!sync.playerList[k].isHost)
						{
							//	we expect this guy to become host.
							if (k === sync.myPlayerInfo._syncID)	//	hey, that's me!
								sync.promoteHost(0);
							break;
						}
					}
				}
			}
		});
	};
	
	/*
	//	A system for autodeleting games when the last player leaves.  If desired!
	//	A work in progress.
	sync.updateAutoDelete = function()
	{
		//	basically, we want an ondisconnect to remove the game from any player connected
		//	IF we're the last player only.
		if (sync.config.autoDelete)
		{
			//	count players.  Would be better to sync this to add/delete events,
			//	but I'm in a hurry.
			var numPlayers = 0;
			for (var k in sync.playerList)
			{
				numPlayers++;
			}
			var doAutoDelete = (numPlayers <= 1);
			
			console.log("autoDelete " + doAutoDelete);
			if (doAutoDelete)
				sync.mRef.onDisconnect().remove();
			else
				sync.mRef.onDisconnect().cancel();
		}
	};
	*/
	
	//	hook up event list
	sync.hookUpEventList = function()
	{
		//	event queue stuff.
		//	todo: move this queue and request functionality to a separate module?  more generally useful?
		//	todo: move this logic earlier so joining player list can queue a useful event we see ourselves.
		
		//	We only want NEW stuff since the instant we connected, not old events from before we arrived.
		//	UTC Timestamps are probably a more reliable way to do this
		sync.eventsRef = sync.mRef.child("_events/");
		//	let's try this - read the last entry one time, and then work from that key forward...
		//	orderbykey is assumed to be the default.
		//	In order to make sure there's an event there that we can reliably skip and wait for the next ones,
		//	let's queue one up now.  Others will also ignore these.
		//	hacky?  I guess.  It's working, though.
		sync.eventsRef.push({syncJoin:true});	//	ignore me.  :)

		sync.eventsRef.limitToLast(1).once('child_added', function(endsnap) {
			//rat.console.log("Q end: " + endsnap.key);
			
			//	note: startAt will include the requested key, and we don't really care about that old event,
			//	we only care about everything after the last one in the list.
			//	thus the sync.eventsReadCount stuff.
			sync.eventsRef.startAt(null, endsnap.key).on('child_added', function(snap) {
				var val = snap.val();
				if (val && sync.handler.handleSyncEvent)
				{
					if (val.syncJoin)	//	special event we queued ourselves.  Ignore it.
						return;
					//rat.console.log("Q <- : " + snap.key + " : " + val.code);
					sync.handler.handleSyncEvent(val);
				}
			});
		});
	};
	
	//	hook up request list
	sync.hookUpRequestList = function()
	{
		sync.requestsRef = sync.mRef.child("_requests/");
		
		//	OK, dang it, this is pissing me off.
		//	Let's try this.  If I'm the host, do it a more intense way - read everything!
		//	client still needs this, but it'll do the version below.
		
		function myReader(snap) {
			//rat.console.log("RY");
			if (!sync.isHost)
				return;
			var val = snap.val();
			if (!val || val.syncJoin)	//	special event we queued ourselves.  Ignore it.
				return;
			
			//	handle requests
			if (val.code === 'own' || val.code === 'disown')	//	we handle these directly
				sync.handleOwnership(val);
			else if (sync.handler.handleSyncRequest)
			{
				//rat.console.log("R <- : " + snap.key + " : " + val.code);
				sync.handler.handleSyncRequest(val);
			}
		}
			
		if (sync.isHost)
		{
			rat.console.log("Aggressive read child approach...");
			sync.requestsRef.on('child_added', myReader);
			return;
		}
		
		//	otherwise
		//	like above...
		
		sync.requestsRef.push({syncJoin:true});	//	ignore me.  :)
		sync.requestsRef.limitToLast(1).once('child_added', function(endsnap) {
			//rat.console.log("RX");
			sync.requestsRef.startAt(null, endsnap.key).on('child_added', myReader);
		});
		/* crap - causes warning I don't understand with database rules.  Fix later.
		//	new approach
		sync.requestsRef = sync.mRef.child("_requests/");
		var now = new Date().getTime();
		var query = sync.requestsRef.orderByChild('timestamp').startAt(now);
		query.on('child_added', function(snap) {
			console.log("req! ");
			if (!sync.isHost)
				return;
			var val = snap.val();
			if (!val)
				return;
			
			rat.console.log("(R) <- : " + snap.key + " : " + val.timestamp);
			
			//	handle requests
			if (val.code === 'own' || val.code === 'disown')	//	we handle these directly
				sync.handleOwnership(val);
			else if (sync.handler.handleSyncRequest)
			{
				rat.console.log("R <- : " + snap.key + " : " + val.code);
				sync.handler.handleSyncRequest(val);
			}
		});
		*/
		
		/*	test jared bug fix
		//	request queue (only read by host, usually)
		//	similar to above, only read most recent?
		//	All nodes listen to this (in case they get promoted), but most of them ignore it.
		sync.requestsRef = sync.mRef.child("_requests/");
		//	slightly cleaner version - if we like this, keep it, and maybe clean up the above?
		//	(don't query for last entry if we are about to add it ourselves!)
		var ref = sync.requestsRef.push({syncJoin:true});	//	ignore me.
		sync.requestsRef.startAt(null, ref.key).on('child_added', function(snap) {
			if (!sync.isHost)
				return;
			var val = snap.val();
			if (!val || val.syncJoin)	//	special event we queued ourselves.  Ignore it.
				return;
			
			//	handle requests
			if (val.code === 'own' || val.code === 'disown')	//	we handle these directly
				sync.handleOwnership(val);
			else if (sync.handler.handleSyncRequest)
			{
				//rat.console.log("R <- : " + snap.key + " : " + val.code);
				sync.handler.handleSyncRequest(val);
			}
		});
		*/
	};
	
	//	hook up chat reading
	sync.hookUpChat = function()
	{
		//	chat system.  Each connected party sees most recent N lines, which we automatically maintain in a list.
		//	unlike above stuff, this includes things people said before we joined...
		//	Chat log is ordered most recent first, so this number N doesn't matter much - game ui can draw what it wants.
		var chatLength = 10;
		sync.chatLog = [];
		sync.chatRef = sync.mRef.child("_chat/");
		//	by listening to child_added events, we might get things in a weird order, like our local pushes
		//	will appear before others.  So, a couple of options here.  Could listen to 'value'
		//	instead of child added.  But for now, let's just maintain our own list and sort it ourselves.
		//	Note, however, that handleChat functions might get confused about proper order to display chat log.
		//	hopefully they're looking at the whole list, in general.
		sync.chatRef.limitToLast(chatLength).on('child_added', function(snap) {
			var val = snap.val();
			if (val) {
				//	whether the handler cares about this event or not, put in our chat log.
				sync.chatLog.unshift(val);
				if (sync.chatLog.length > chatLength)
					sync.chatLog.pop();
				
				//	and then handler
				if (sync.handler.handleChat)
					sync.handler.handleChat(val);
				
				sync.chatLog.sort(function(a, b) {
					return b.timestamp - a.timestamp;
				});
			}
		});
	};
	
	//	write to or listen to match config
	sync.hookUpMatchConfig = function()
	{
		//	if we're host and have matchmaking info, we should store that as well...
		//	Everyone should read this so they can correctly host migrate
		sync.matchRef = sync.mRef.child("_match/");
		if (sync.isHost && sync.config.matchConfig)
		{
			sync.config.matchConfig.id = sync.config.id;	//	make sure id is there?  I'm not sure this is useful...
			sync.matchRef.set(sync.config.matchConfig);
		} else {	//	and otherwise read it, eventually...
			sync.matchRef.on('value', function(snap) {
				sync.config.matchConfig = snap.val();
				rat.console.log("got matchconfig: " + sync.config.matchConfig);
				//	sync.handler.handleMatchConfig ?
			});
		}
	};
	
	//	ownership list management
	sync.setupOwnershipTracking = function()
	{
		sync.ownList = {};
		sync.ownRef = sync.mRef.child("_own/");

		//	primarily, things only get added to and removed from this list, right?
		//	A really lazy approach would be to just listen to value changes on the list.
		//	Let's do that.  :)
		sync.ownRef.on('value', function(snap) {
			var ownList = snap.val();
			if (!ownList)
				ownList = {};
			sync.ownList = ownList;
		});
	};
	
	sync.iOwn = function(object)
	{
		if (!object._syncID)
			return false;
		if (sync.ownList[object._syncID] === sync.myPlayerInfo._syncID)
			return true;
		return false;
	};
	
	sync.getOwner = function(object)
	{
		if (!object._syncID)
			return 0;
		return sync.ownList[object._syncID];
	};
	
	//	handle ownership requests.
	//	We used to implement this with data in the owned object itself,
	//	but that was a little hacky and unreliable - changes to ownership would overwrite data
	//	and changes to data would overwrite ownership, despite several attempts to clean that up.
	//	(One problem is that we only read entire objects at once, so owner would get overwritten easily)
	//	But we need ownership to be reliable.
	//	So, now we maintain a whole separate entry in the database that indicates ownership by syncID
	//	The host is the only node that is allowed to write to this.
	sync.handleOwnership = function(val)
	{
		if (val.code === 'own')
		{
			if (sync.ownList[val.params.syncID])	//	already owned!
				return false;
			sync.ownRef.child(val.params.syncID).set(val.params.playerID);
			//rat.console.log(" O+ " + val.params.playerID + " owns " + val.params.syncID);
			return true;
		} else if (val.code === 'disown')
		{
			if (sync.ownList[val.params.syncID] === val.params.playerID)	//	correct owner
			{
				sync.ownRef.child(val.params.syncID).set(null);	//	remove
				//rat.console.log(" O-" + val.params.playerID + " disowns " + val.params.syncID);
			}
			return true;
		}
		return false;
		
		/*old
		//	find the object this belongs to...
		
		var o;
		var mapEntry = sync.map[val.params.key];
		if (mapEntry)
		{
			if (mapEntry.isArray)
			{
				var index = sync.findInSourceListByID(mapEntry.sourceData, val.params.syncID);
				if (index >= 0)
					o = mapEntry.sourceData[index];
			}
			else
				o = mapEntry.sourceData;
		}
		if (!o)	//	couldn't find it.
			return false;
			
		if (val.code === 'own')
		{
			rat.console.log("got own request for " + val.params.syncID + " from " + val.params.playerID);
			if (!o._syncOwner)
			{
				rat.console.log("awarded ownership.");
				//o._syncOwner = val.params.playerID;
			}
			return true;
		} else if (val.code === 'disown')
		{
			console.log("got disown request for " + val.params.syncID + " from " + val.params.playerID);
			console.log("   has pos " + o.target.x + "," + o.target.y);
			if (o._syncOwner === val.params.playerID)
			{
				rat.console.log("released ownership.");
				o._syncOwner = 0;
			}
			return true;
		}
		
		return false;
		*/
	};

	//	demote me from host status.
	//	only called if host migration is enabled
	sync.demoteHost = function()
	{
		rat.console.log("demote me from host.");
		if (!sync.isHost)
			return;
		
		sync.isHost = false;
		//	we're presumably disconnected and therefore
		//	removed from player list.  No need to write a chance to that.
		//	change our local player info.
		sync.myPlayerInfo.isHost = false;
		
		//	switch our read/write flags
		sync.updateOperations();
		
		if (sync.handler && sync.handler.demoteHost)
			sync.handler.demoteHost();
	};
	
	//	promote me to host status.
	//	only called if host migration is enabled
	sync.promoteHost = function(timeSinceLastHostUpdate)
	{
		rat.console.log("promote me to host.");
		
		sync.isHost = true;
		sync.myPlayerInfo.isHost = true;	//	this gets written back out below...
		
		//	switch our read/write operation flags
		sync.updateOperations();
		
		//	re-list with matchmaking service
		if (sync.config.matchConfig && sync.config.matchConfig.id && sync.config.matchConfig.isPublic && rat.live.match)
		{
			rat.live.match.init(sync.projectRoot);
			rat.live.match.hostGameWithID(sync.config.matchConfig.id, sync.config.matchConfig.name, sync.config.matchConfig);
			rat.live.match.shutDown();
		}
		
		//	tell game about this change that's happening now.
		//	Calling this before setting player info is intentional,
		//	so the game will hear about player list changes and know it's the host,
		//	so it can do proper player list processing.
		if (sync.handler && sync.handler.promoteHost)
			sync.handler.promoteHost(timeSinceLastHostUpdate);
		
		//	update my player data with new isHost flag
		//	(do this after all the above, so it's most useful)
		sync.myPlayerRef.set(sync.myPlayerInfo);
	};
	
	sync.updateOperations = function()
	{
		for (var k in sync.map)
		{
			var entry = sync.map[k];
			entry.operations = sync.isHost ? entry.hostOperations : entry.clientOperations;
		}
	};
	
	//	I'm connecting - write my player info and set OnDisconnect().remove flag.
	//	This may get called more than once, e.g. if we connect, disconnect, and reconnect.
	//	Note that we write whatever we have in our playerInfo structure,
	//	so it could include some game-specific data in addition to standard fields.
	sync.connectMyPlayerInfo = function()
	{
		sync.myPlayerRef.onDisconnect().remove();
		
		sync.myPlayerInfo.isHost = !! sync.myPlayerInfo.isHost;	//	make sure this is set explicitly
		sync.myPlayerRef.set(sync.myPlayerInfo);
		
		//	old - only write out some values
		//sync.myPlayerRef.set({
		//	'name' : sync.myPlayerInfo.name,
		//	'isHost' : !! sync.myPlayerInfo.isHost
		//});
	};
	
	sync.updatePlayer = function(playerID, newData)
	{
		var pRef = sync.playersRef.child(playerID);
		//	use a transaction because I want to NOT re-add the player if client disconnected right before this!
		pRef.transaction(function(currentData) {
			if (currentData)
			{
				return newData;
			} else
				return;	//	abort transaction - that player was deleted
		});
	};
	
	/*
		prop flags for properties of registered object
	
			change: means write on change.  This is the default value of 1.
			
			writeNext : write this value next opportunity, and then clear this flag.
				mostly set internally - if you want to write the next value, call markChanged()
			
			init: means we write and read that value ONCE at startup and then ignore it from then on.
				(flag exists, but implementation isn't done)
				If the value isn't changing, this may not be useful in any case.
				That's already what happens to non-changing values.
				However, explicitly marking something 'init' means we won't even check for changes later,
				which could be optimal.  How is this different from operations.write_init below?
	
			noRead: means we write it and never read it on a client. (meant for external use)
				UNIMPLEMENTED and unused
	
			reckon: means write initial value, occasionally write changes, and on client side, reckon the current value
				based on changes.  This includes handling structures with multiple values (e.g. vectors)
				UNIMPLEMENTED, but recognized - right now it just means the value is not written unless forced.
				
			low: low priority data - only update sometimes, on a timer.
				we probably don't want a big packet all at once, but on the other hand, one occasional big packet can probably be handled more optimally
				than small packets every frame?
	
				This is roughtly implemented, but doesn't work well for some things.
				For moving objects being interpolated on client, it makes them jerk around,
				because of network latency in getting the "current" value and immediately resetting to that when received.
	
			short: truncate or simplify value? (e.g. don't write out big fractional values, which are sent as strings?  I don't know...)
		
	*/
	//	These are implemented as bitflags so we can have more than one at a time.
	sync.prop = {};
	sync.prop.none = 0,				//	don't write this prop
	sync.prop.change = 1,			//	most common value - write when changed.
	sync.prop.init = (1<<1),		//	don't write this prop
	sync.prop.writeNext = (1<<2),	//	unused!
	sync.prop.low = (1<<3),			//	write only occasionally
	sync.prop.reckon = (1<<4),		//	reckon this value (unimplemented - currently doesn't ever write unless forced)
	
	//	register this object for future writes (and reads?)
	//	we only care about the list of properties given,
	//	so the original data may be more complex than this, but only a subset of data is synced.
	//	This lets the game organize data however it wants, and still have key stuff synced.
	//	TODO:	"props" can have subelements, so we trace it all the way down
	//	If a property is named in props and turns out to be an object in data,
	//	we sync that whole object.
	
	//	Arrays:
	//		You can register an array.
	//		"props" refers to the properties of each individual array entry.
	//		We try to read and write individual items in the array instead of the whole array, where possible.
	
	//	Props is an object with properties for each property we want to copy.
	//	if a field in props exists, we care about that property.
	//	if a field in props doesn't exist, we don't care, generally.
	
	//	TODO: (unimplemented)
	//	PROP TYPES:
	//		The value of props['whatever'] can be several things...
	//		Any defined non-zero value means 'we care about this'.
	//
	//		if the value is an object:  there are more recursive props to worry about
	//		Otherwise, see list of prop flags above!
	
	//	todo: refactor into subfunctions for readability
	sync.register = function(key, sourceData, options)
	{
		//	process arguments...
		var props = options ? options.props : null;
		if (!props)
		{
			var defProps = sync.pickDefaultProps(sourceData);
			props = defProps.props;
			if (defProps.propCount === 0)
			{
				//	special case - we don't know anything about the data, and no props were specified,
				//	so let's always sync everything.
				//	This is entirely to make life easier for the game code,
				//	since normally we would prefer all properties to be specified.
				//	set props to "undefined" and sync system will automatically check all properties.
				props = void 0;
				rat.console.log("WARNING: defaulting to all props for " + key);
			}
		}
		else
			props = rat.utils.copyObject(props, true);	//	make a copy in case we change it
		
		//	default to assumed behavior for host and non-host types,
		//	which is that everybody reads and writes, but only host writes initially.
		//	This seems awfully complicated... :(
		var hostOperations = options ? options.hostOperations : null;
		if (!hostOperations)
			hostOperations = {write_init:true, write:true, read:true};
		var clientOperations = options ? options.clientOperations : null;
		if (!clientOperations)
			clientOperations = {write_init:false, write:true, read:true};
		var operations = sync.isHost ? hostOperations : clientOperations;
		
		var readCallback = options ? options.readCallback : null;
		var lowTimer = options ? options.lowTimer : void 0;
		if (!lowTimer)
			lowTimer = 3;	//	default to 3-second timer for low-priority data.
		
		var isArray = Array.isArray(sourceData);
		
		//	this ref refers to this data on server
		var ref = sync.mRef.child(key);
		
		//	register in our map
		sync.map[key] = {
			sourceData : sourceData,
			props : props,
			curData : {},	//	set below
			ref : ref,
			operations : operations,	//	operations currently being used
			hostOperations : hostOperations,	//	if we become host (host migration)
			clientOperations : clientOperations,	//	if we become client (host migration)
			isArray : isArray,		//	some special handling in various places
			lowTimer : lowTimer,	//	for 'low' priority properties. May be null
			lowTimerMax : lowTimer,
		};
		
		//	track our initial values of everything
		//	And, if write_init, then actually write initial value to database.
		//		do this BEFORE registering listeners, so we don't get duplicate copies of things...
		if (operations.write_init || operations.write)
		{
			sync.debugLog("write initial value " + key);
			if (isArray)
			{
				if (!operations.write_init && sourceData.length > 0)
				{
					rat.console.log("rat sync ERROR: please don't register an array with write but no write_init");
					//	The problem here is that if you're not sending out an initial copy of your array,
					//	we can't get unique firebased IDs for it.
					//	So, we expect you to either NOT HAVE any data yet (that's fine),
					//	or be writing that data out immediately (write_init)
				}
				
				for (var i = 0; i < sourceData.length; i++)
				{
					var curData = sync.copyProps(sourceData[i], props);
					var newRef = ref.push(curData);
					syncID = newRef.key;
					
					sourceData[i]._syncID = newRef.key;
					sync.map[key].curData[syncID] = curData;	//	remember what we wrote to watch for changes
				}
			} else {	//	simpler case - not array
				//	get our own copy of this data so we can detect changes later.
				var curData = sync.copyProps(sourceData, props);
				sync.map[key].curData = curData;
				
				//	and write it now
				if (operations.write_init)
					ref.set(curData);
			}
		}
		
		//	set up reads
		//	Note that there's a lot of closure reference in here, so if somehow sourceData is replaced, we'll be referring to the wrong thing.
		//	todo: separate operations for "replace my data" and "notify only" (call my callback)?
		//	todo: several places below, we read curData, but that's only needed if write operation is set, right?
		//	otherwise, we never check against curData.
						
		if (operations.read)
		{
			if (isArray)
			{
				//	and listen to add/delete/change
				ref.on('child_added', function(snap) {
					
					//	In other places below, we may push a value to the database after we already have it locally.
					//	In that case, don't go reading it here.
					if (sync.suppressRead === key)
						return;
					
					var mapEntry = sync.map[key];
					sync.debugLog("read: child_added " + key + " : " + snap.key);
					//	add to our list *if it doesn't already exist* (e.g. we're the server and already started with this value)
					var val = snap.val();
					var syncID = snap.key;
					var index = sync.findInSourceListByID(sourceData, syncID);
					if (index < 0)	//	new to us, as well.
					{
						mapEntry.curData[syncID] = val;
						val._syncID = syncID;
						
						//	directly set sourceData with the only props we know about so far.
						//	game had better be able to deal with getting potentially sparse data like this.
						//	Also, it's important that this is a copy of the data and not a reference.
						//	The game will go changing this data, and we need it to be distinct from our curData value,
						//	because that's how we watch for changes.
						var newSourceData = rat.utils.copyObject(val, true);
						sourceData.push(newSourceData);
						
						//	TODO: call add callback, referencing the data we just added!
					}
				});

				ref.on('child_removed', function(snap) {
					var mapEntry = sync.map[key];
					sync.debugLog("read: child_removed " + key + " : " + snap.key);
					
					var index = sync.findInSourceListByID(sourceData, snap.key);
					if (index >= 0)
					{
						//	TODO: call user-provided delete callback, probably here before deleting
						delete mapEntry.curData[snap.key];
						sourceData.splice(index, 1);
					}
				});

				ref.on('child_changed', function(snap) {
					var mapEntry = sync.map[key];
					var syncID = snap.key;
					var index = sync.findInSourceListByID(sourceData, syncID);
					sync.debugLog("read: child_changed " + key + " : " + snap.key + " :#" + index);
					if (index >= 0)	//	it should always be in our list, in theory...
					{
						//	apply changes back to source game data
						var val = snap.val();
						if (!mapEntry.isWriting)
							sync.applyProps(val, mapEntry.sourceData[index], mapEntry.props);
						
						//	and replace our own known data to watch for changes...
						mapEntry.curData[syncID] = sync.copyProps(val, mapEntry.props);
						
						//	callback, if any, and if we're not actively locally writing
						if (readCallback && !mapEntry.isWriting)
						{
							readCallback(key, val, index);
						}
					}
				});
				
			} else {	//	simpler case - just respond to all data changes
				//	this change handler function depends on closure values from above,
				//	like key and readCallback function reference.
				ref.on('value', function(snap)
				{
					var mapEntry = sync.map[key];
					sync.debugLog("read: value for '" + key + "' : " + snap.key);
					//	apply changes back to source game data
					var val = snap.val();
					if (!val)
					{
						//	weird case - data was deleted?  Shouldn't ever happen, normally.
						//	Happens if data never existed in the database, but we listen to value on it?
						//	Might happen if game host doesn't set write_init like it should...
						//	Anyway, if there's no data, don't read it.
						return;
					}
					if (!mapEntry.isWriting)
						sync.applyProps(val, mapEntry.sourceData, mapEntry.props);
					
					//	update our own known data to watch for changes...
					mapEntry.curData = sync.copyProps(val, mapEntry.props);
					
					//	callback, if any
					if (readCallback && !mapEntry.isWriting)
					{
						readCallback(key, val);
					}
				});
			}
		}
		
	};

	//	pick all reasonable props from this top-level object
	//	return an object containing props and info about props collected.
	sync.pickDefaultProps = function(sourceData)
	{
		if (Array.isArray(sourceData))
			sourceData = sourceData[0];	//	look at first object in the array as an example
		if (!sourceData)	//	can't guess if we have no initial data.
			return {propCount:0, props:{}};
		var props = {};
		var propCount = 0;
		for (var k in sourceData)
		{
			if (!sourceData.hasOwnProperty(k))
				continue;
			if (typeof(sourceData[k]) === undefined || typeof(sourceData[k]) === 'function')
				continue;

			propCount++;
			props[k] = 1;
		}
		
		return {propCount:propCount, props:props};
	};
	
	//	util to search by sync id
	sync.findInSourceListByID = function(sourceData, syncID)
	{
		for (var i = 0; i < sourceData.length; i++)
		{
			if (sourceData[i]._syncID === syncID)
				return i;
		}
		return -1;
	};
	
	//	make a new copy of this data, but only named props
	sync.copyProps = function(sourceData, props)
	{
		//	array handling - make new array, using props to refer to each entry.
		if (Array.isArray(sourceData))
		{
			var newList = [];
			for (var i = 0; i < sourceData.length; i++)
			{
				var newData = sync.copyProps(sourceData[i], props);
				newList.push(newData);
			}
			return newList;
		}
		
		//	not array - normal object handling
		var curData = {};
		for (var k in props)
		{
			//	always skip undefined values and functions
			var sType = typeof(sourceData[k]);
			if (sType === 'undefined' || sType === 'function')
				continue;

			if (sType === 'object')
			{
				//	todo: check type of props[k] - if object, recurse (using this func).
				//	only do this full subcopy if props[k] is defined but not an object...
				//	note that when we add that, arrays and subarrays will get trickier!
				//	props won't have arrays in it, right?  Anywhere there's an array in the source data,
				//	props will instead have an *object*
				//		and that object will refer to how to handle each array entry.
				//	todo: We might need our own version of copyObject here,
				//		since we need to guarantee there are no function refs in here for firebase to choke on.
				//	which firebase will choke on.
				curData[k] = rat.utils.copyObject(sourceData[k], true);
			}
			else
				curData[k] = sourceData[k];
		}
		return curData;
	};
	
	//	copy props, recursively, from one object to another, without destroying other existing values.
	//	todo: This should be a rat util! (if we're ignoring props argument anyway)
	sync.applyProps = function(fromData, toData, props)
	{
		//	ignoring props object for now.  Just look for anything in fromData and copy it over with prejudice.
		for (var k in fromData)
		{
			if (typeof(fromData[k]) === 'object')	//	including arrays
			{
				if (!toData[k] || typeof(toData[k]) !== 'object')
					toData[k] = {};
				sync.applyProps(fromData[k], toData[k], null);	//	again, ignoring props argument for now
			} else
				toData[k] = fromData[k];
		}
	};
	
	//	Update internal timers
	sync.update = function(dt)
	{
		var oldTimer = sync.timer;
		sync.timer += dt;
		
		for (var k in sync.map)
		{
			var entry = sync.map[k];
			//	count timer down, and then flag "updateLow" meaning we'll write out the next change we see in low props.
			//	only after that do we start counting again.
			//	So, a low value that changes has to wait for next timer expiration to write.
			if (entry.lowTimer && !entry.updateLow)
			{
				entry.lowTimer -= dt;
				if (entry.lowTimer <= 0)
				{
					entry.updateLow = true;
					entry.lowTimer = entry.lowTimerMax;
				}
			}
		}
		
		//	every n seconds, check for inactive/missing host
		if (sync.config.hostMigration && sync.config.matchConfig && ((oldTimer / 2)|0) !== ((sync.timer / 2)|0))
		{
			sync.checkActiveHost();
		}
		//	every n seconds, check for inactive/missing object owners
		if (sync.isHost && ((oldTimer / 2)|0) !== ((sync.timer / 2)|0))
		{
			sync.checkActiveOwners();
		}
		
		//	every n seconds, update server heartbeat...
		var checkSpeed = sync.config.hostHeartbeatFrequency;
		if (checkSpeed > 0 && sync.isHost && ((oldTimer / checkSpeed)|0) !== ((sync.timer / checkSpeed)|0))
		{
			sync.writeHostHeartbeat();
		}
	};
	
	//	write out a host heartbeat (timestamp) so other players (and other systems) know
	//	how long ago the game was updated by the host.
	sync.writeHostHeartbeat = function()
	{
		var d = new Date();
		var timeString = d.toUTCString();
		
		var heartValues = 
		{
			'hostHeartbeat' : d.getTime(),
			'hostHeartbeatString' : timeString,
		};
		//rat.console.log("host heartbeat " + heartValues.hostHeartbeatString + " : " + heartValues.hostHeartbeat);
		sync.statsRef.update(heartValues);
	};
	
	//	check if the host is missing or inactive in the player list.
	sync.checkActiveHost = function(key, newData)
	{
		//	if I'm not connected, then my info is out of date, and there's definitely no host.
		if (!sync.isConnected())
			return;
		//	similarly, don't try to become host if we haven't read any game data yet
		//	and don't know what's going on.
		if (!sync.gameStats)
			return;
		
		var haveHost = false;
		var firstNonHostID;
		for (var k in sync.playerList)
		{
			if (sync.playerList[k].isHost)
			{
				haveHost = true;
			} else if (!firstNonHostID)
				firstNonHostID = k;
			
		}
		if (!haveHost)
		{
			rat.console.log("detected no host");
			//	do it.  But as with other host migration code, we depend on the future king to recognize himself.
			//	could be us!  is it us?  oh boy!
			if (firstNonHostID === sync.myPlayerInfo._syncID)	//	hey, that's me!
			{
				//	figure out how long it's been since a host updated the game.
				var timeSinceLastHostUpdate = 0;
				if (sync.gameStats.hostHeartbeat)
				{
					var d = new Date();
					var ms = d.getTime() - sync.gameStats.hostHeartbeat;
					timeSinceLastHostUpdate = ms / 1000;
				}
				
				sync.promoteHost(timeSinceLastHostUpdate);
			}
		}
		//	todo: if the host hasn't updated in a while, force demotion. See notes at top of file.
	};
	
	//	check if an owner is missing or inactive in the player list.
	//	todo: inactivity
	sync.checkActiveOwners = function()
	{
		var updates = {};
		var haveUpdates = false;
		for (var k in sync.ownList)
		{
			var pid = sync.ownList[k];
			if (!sync.playerList[pid])
			{
				updates[k] = null;	//	remove
				haveUpdates = true;
				rat.console.log("Dead owner - object available again.");
			}
		}
		if (haveUpdates)
			sync.ownRef.update(updates);
	};
	
	//	inform the sync system that this object changed
	//	(write it right now)
	//	With arrays, we assume you're specifying a single entry by its data (which we expect has a sync ID in it)
	//	We also assume you're not updating an object that JUST got added...
	/*
	sync.updateObject = function(key, newData)
	{
		var mapEntry = sync.map[key];
		if (!mapEntry || !mapEntry.operations.write)
			return;
			
		if (mapEntry.isArray)	//	we expect ONE data entry...
		{
			var syncID = newData._syncID;
			mapEntry.curData[syncID] = sync.copyProps(newData, mapEntry.props);
			//	just set child data in database
			mapEntry.ref.child(syncID).set(mapEntry.curData[syncID]);
						
		} else {
			if (!newData)
				newData = mapEntry.sourceData;
			mapEntry.curData = sync.copyProps(newData, mapEntry.props);
			mapEntry.ref.set(mapEntry.curData);
		}
	};
	*/
	
	//	If this registered object changed, write new changes out.
	sync.updateObjectIfChanged = function(key, newData)
	{
		var mapEntry = sync.map[key];
		if (!mapEntry)
			return;
		if (!mapEntry.operations.write)
			return;
		
		//	track different kinds of updates, and write them at the end with an update()
		var writeUpdates = false;
		var updates = {};
		//var reckons = {};
		//var lows = {};
		
		//	you can call sync.updateObjectIfChanged('mykey') without specifying data,
		//	since we know what data you mean (the source data object you registered)
		if (!newData)
			newData = mapEntry.sourceData;
		
		//	util to check data (recursively) for changes and collect them into the updates variable above.
		//	return true if there were any changes collected
		function checkWriteChange(oldData, newData, propFlags, path)
		{
			var haveChanges = false;
				
			//	if we ever get an array deep in an object, and explicit propFlags to check, 
			//	that means apply those propFlags to each entry in the array,
			//	so deal with that here.
			//	Any change to the length of an array results in the whole array being written out.
			if (Array.isArray(newData) && propFlags !== void 0)
			{
				if (oldData.length !== newData.length)
				{
					//	write the whole array all at once now?
					//	Do we need to sanitize that data somehow?
					sync.debugLog("write [] " + path + " (whole array) ");
					updates[path] = newData;
					writeUpdates = true;
				}
				
				for (var i = 0; i < newData.length; i++)
				{
					//	depend on firebase interpreting numbered sequential keys as array entries...
					if (checkWriteChange(oldData[i], newData[i], propFlags, path + "/" + i))
						haveChanges = true;
				}
				return haveChanges;
			}
			
			//	If explicit list of properties was given, then use that.
			//	otherwise, just check all subelements of the newData.
			//	TODO: what if new data is *missing* something that was in old data?
			//		e.g. newly deleted.
			//		That's not handled.  FIX THIS

			var propList = propFlags;
			if (propList === void 0)
				propList = newData;
			//	TODO: this is questionable.  propList below is treated as our special custom prop list.
			//	to just assign it to newdata like this is asking for trouble.
			//	more likely, set some other flag saying "grab it all"?
			//	or manually build a new proplist with default sync types from newData?  (we already have a func to do that)
			//	That's a lot of work.
			//	For now, we just make sure if we're checking flags, we check original argument "propFlags", not propList.

			for (var k in propList)
			{
				var localChanged = false;

				//	figure out if we should consider this value changed.

				//	if there are specific defined flags for this field, look at them.
				var myCheckFlags = 0;
				if (propFlags && propFlags[k] && typeof(propFlags[k]) !== 'object')
					myCheckFlags = propFlags[k];
				
				if (myCheckFlags & sync.prop.writeNext)
				{
					localChanged = true;
					propFlags[k] &= ~sync.prop.writeNext;	//	clear that for next time
					//	and ignore some other flags that would stop us from writing.
					myCheckFlags &= sync.prop.reckon;
					myCheckFlags &= sync.prop.low;
				}

				//	if not already marked changed, then throw away anybody marked as reckon (todo: reckoning)
				if (myCheckFlags & sync.prop.reckon)
					continue;
				
				var n = newData[k];
				var o = oldData[k];
				
				//	todo: subarrays?
				//	todo: if propFlags here is an object, it means selectively check subelements,
				//		so, um... that's fancier and harder...  do that at some point.
				//	in the mean time (and otherwise), just copy everything from here down.
				
				//	todo: don't write functions or undefined values.
				
				//	quick check for things like existence...
				if ((n && !o) || (o && !n) || typeof(o) !== typeof(n))
					localChanged = true;	//	changed!
				
				//	so, if localChanged at this point, we're going to write the whole subobject at once.
				//	Only check properties recursively if we still haven't seen a change.
				if (!localChanged && typeof(n) === 'object')	//	need to check object properties recursively
				{
					//	TODO: what if our subobject has function references or undefined values?
					//	that'll choke firebase, and we want to fix it.
					//	so, as we walk through subelements, keep an eye on that somehow.
					//	Already handled above, because this is recursive?
					var checkProps = void 0;
					if (propFlags && typeof(propFlags[k]) === 'object')	//	subprops available?
						checkProps = propFlags[k];	//	keep using sub props if they're there
					if (checkWriteChange(o, n, checkProps, path + "/" + k))
						haveChanges = true;
				}
				else if (n !== o)
					localChanged = true;
				
				if (localChanged)
				{
					//	if this is a low-priority change and we're not writing lows,
					//	then skip this.  Otherwise, write it now!
					if ((myCheckFlags & sync.prop.low) && !mapEntry.updateLow)
						continue;
					
					//sync.debugLog("write-> " + key + " .. " +  (changed)");
					sync.debugLog("write-> " + path + " . " + k);
					//	Let's let our read callback collect this.
					//mapEntry.curData = sync.copyProps(newData, mapEntry.props);
					//	set individual value
					updates[path + "/" + k] = n;
					writeUpdates = true;
					
					haveChanges = true;
				}
				
			}
			return haveChanges;
		}
		
		//	util to check and write one block of data
		function checkOneBlock(oldData, newData, props, ref)
		{
			updates = {};
			writeUpdates = false;
			
			//	path is "" because we're going to update() relative to this individual entry...
			checkWriteChange(oldData, newData, props, "");
			
			//	special requested writes?
			if (newData._syncUpdates)
			{
				//	copy them straight over
				for (var k in newData._syncUpdates)
				{
					updates[k] = newData._syncUpdates[k];
				}
				delete newData._syncUpdates;	//	that's a one-time thing
				writeUpdates = true;
			}
			
			if (writeUpdates)
			{
				ref.update(updates);
			}
		}
		
		//	as we write local changes, don't try to tell the game about them as they
		//	get read back in by our firebase callbacks!
		//	we're already doing all the work to push out our local changes.
		//	The game already knows about these changes, since it made them.
		mapEntry.isWriting = true;
		
		//	list handling: if it's an array, only write the entries that changed.
		if (mapEntry.isArray)
		{
			for (var i = 0; i < newData.length; i++)
			{
				//	each entry gets its own update call, so start over with what's being written
				updates = {};
				//reckons = {};
				//lows = {};

				var syncID = newData[i]._syncID;
				if (!syncID)
				{
					//	new thing we've never seen.  This is a change we need to write.
					var newCurData = sync.copyProps(newData[i], mapEntry.props);
					sync.suppressRead = key;	//	we're about to push, which complicates firebase change callback code
					var newRef = mapEntry.ref.push(newCurData);
					sync.suppressRead = 0;
					syncID = newRef.key;
					newData[i]._syncID = newRef.key;	//	store syncID in game data for future comparisons
					mapEntry.curData[syncID] = newCurData;	//	remember what we wrote to watch for changes
					
					sync.debugLog("write-> " + key + " child " + syncID + " (new)");
					
				} else {	//	normal array entry we already knew about.  See if it changed.
					
					var curSubData = mapEntry.curData[syncID];	//	last state we knew about	
					checkOneBlock(curSubData, newData[i], mapEntry.props, mapEntry.ref.child(syncID));
				}
			}
			
			//	figure out if anything was deleted, and delete it on server.
			//	If something appears in curdata but not in source data, it was removed from source data
			for (var k in mapEntry.curData)
			{
				var index = sync.findInSourceListByID(newData, k);
				if (index < 0)
				{
					sync.debugLog("delete-> " + key + " child " + k);
					delete mapEntry.curData[k];	//	delete my copy
					mapEntry.ref.child(k).remove();	//	remove from server
				}
			}
			
		} else {
			//	easy case - it's a single whole object.
			checkOneBlock(mapEntry.curData, newData, mapEntry.props, mapEntry.ref);
		}
		
		mapEntry.isWriting = false;
		
		//	by now, any low pri stuff had a chance to write.
		mapEntry.updateLow = false;
	};
	
	
	//
	//	TODO: 
	//		I feel like we ought to be writing only changes,
	//		and then updating curData only when we read those changes in our earlier-registered
	//		callbacks, which we need to do anyway, right?
	//		maybe also do the "I'm writing, don't tell game about changes" flag at the same time.
	//
	//		Try it.  See if it improves our ownership issues...,
	//			since ownership field will only ever get written by server, in that case.
	//
	
	/*	OLD VERSION FOR REFERENCE
	sync.updateObjectIfChanged = function(key, newData)
	{
		var mapEntry = sync.map[key];
		if (!mapEntry)
			return;
		if (!mapEntry.operations.write)
			return;
		
		if (!newData)
			newData = mapEntry.sourceData;
		
		//	util to check data (recursively) for changes
		function checkChange(oldData, newData, props)
		{
			//	if we ever get an array, including deep in an object, and explicit props to check, 
			//	that means apply those props to each entry in the array,
			//	so deal with that here.
			if (Array.isArray(newData) && props !== void 0)
			{
				if (oldData.length !== newData.length)
					return true;
				
				for (var i = 0; i < newData.length; i++)
				{
					if (checkChange(oldData[i], newData[i], props))
						return true;
				}
				return false;
			}
			
			//	If explicit list of properties was given, then use that.
			//	otherwise, just check all subelements of the newData.
			//	TODO: what if new data is  *missing* something that was in old data?
			//		e.g. newly deleted.
			//		That's not handled.  FIX THIS

			var propList = props;
			if (propList === void 0)
				propList = newData;
			//	TODO: this is questionable.  propList below is treated as our special custom prop list.
			//	to just assign it to newdata like this is asking for trouble.
			//	more likely, set some other flag saying "grab it all"?
			//	or manually build a new proplist with default sync types from newData?  Hrm...
			
			for (var k in propList)
			{
				var n = newData[k];
				var o = oldData[k];
				
				//	todo: subarrays?
				//	todo: if props here is an object, it means selectively check subelements,
				//		so, um... that's fancier and harder...  do that at some point.
				//	in the mean time (and otherwise), just copy everything from here down.
				
				//	todo: check more prop flags...
				if (propList[k] === 'reckon')
					continue;	//	let's just not update these at all for now.
				
				var nType = typeof(n);
				//	always ignore undefined values or functions, even if they were explicitly named.
				//	they can't be written to firebase.
				if (nType === 'undefined' || nType === 'function')
					continue;
				
				var localChanged = false;
				//	quick check for things like existence...
				if ((n && !o) || (o && !n) || typeof(o) !== nType)
					localChanged = true;	//	changed!
				
				else if (nType === 'object')	//	need to check object properties recursively
				{
					var checkProps = void 0;
					//if (props !== void 0)
					if (props && typeof(props[k]) === 'object')	//	subprops available?
						checkProps = props[k];	//	keep using sub props if they're there
					if (checkChange(o, n, checkProps))
						localChanged = true;
				} else {
					if (n !== o)
						localChanged = true;
				}
				
				if (localChanged)
				{
					//	watch for low-priority changes.  Should we be reporting those?
					if (propList[k] === 'low')
					{
						if (mapEntry.updateLow)
							return true;
						else
							continue;
					}
					
					return true;
				}
				
			}
			return false;
		}
		
		//	list handling: if it's an array, only write the entries that changed.
		if (mapEntry.isArray)
		{
			for (var i = 0; i < newData.length; i++)
			{
				var changed = false;
				var syncID = newData[i]._syncID;
				if (!syncID)
				{
					//	new thing we've never seen.
					var newCurData = sync.copyProps(newData[i], mapEntry.props);
					sync.suppressRead = key;	//	we're about to push, which complicates read code above...
					var newRef = mapEntry.ref.push(newCurData);
					sync.suppressRead = 0;
					syncID = newRef.key;
					newData[i]._syncID = newRef.key;
					mapEntry.curData[syncID] = newCurData;	//	remember what we wrote to watch for changes
					
					sync.debugLog("write-> " + key + " child " + syncID + " (new)");
					
				} else {
					var curSubData = mapEntry.curData[syncID];
					changed = checkChange(curSubData, newData[i], mapEntry.props);
					if (changed)
					{
						sync.debugLog("write-> " + key + " child " + syncID + " (changed)");
						mapEntry.curData[syncID] = sync.copyProps(newData[i], mapEntry.props);
						//	just set child data in database
						mapEntry.ref.child(syncID).set(mapEntry.curData[syncID]);
					}
				}
			}
			
			//	figure out if anything was deleted, and delete it on server.
			//	If something appears in curdata but not in  source data, it was removed from source data
			for (var k in mapEntry.curData)
			{
				var index = sync.findInSourceListByID(newData, k);
				if (index < 0)
				{
					sync.debugLog("delete-> " + key + " child " + k);
					delete mapEntry.curData[k];	//	delete my copy
					mapEntry.ref.child(k).remove();	//	remove from server
				}
			}
			
		} else {
			//	easy case - it's a single whole object.
			var changed = checkChange(mapEntry.curData, newData, mapEntry.props);
			
			if (changed)
			{
				sync.debugLog("write-> " + key + " (changed)");
				mapEntry.curData = sync.copyProps(newData, mapEntry.props);
				mapEntry.ref.set(mapEntry.curData);
			}
		}
		
		mapEntry.isWriting = false;
		
		//	by now, any low pri stuff had a chance to write.
		mapEntry.updateLow = false;
	};
	*/
	
	//	one easy option:
	//	ask the sync system to check ALL objects and update any changed.
	//	For very complex data, this might end up being a lot of work!
	sync.updateAllChanges = function()
	{
		for (var k in sync.map)
		{
			var entry = sync.map[k];
			sync.updateObjectIfChanged(k, entry.sourceData);
		}
	},
	
	//	mark this specific property changed for the next write,
	//	so it'll get written for sure, even if it was marked as 'low' or 'reckon' or something.
	//	The ideal implementation of this is currently unclear...
	//	So, I'm currently collecting up new update references to add to the firebase update call that happens when the object is updated...
	//	*** Currently works only for top-level properties!
	//	usage:  sync.markChanged('actors', game.actors[3], 'pos');
	sync.markChanged = function(key, object, propName)
	{
		object._syncUpdates = object._syncUpdates || {};
		object._syncUpdates["/" + propName] = object[propName];
	
		//var mapEntry = sync.map[key];
		//if (mapEntry.isArray)
		//{
		//var index = sync.findInSourceListByID(mapEntry.sourceData, k);
		//		if (index < 0)
		//	blah
	},
	
	//	request queue handling...
	sync.queueRequest = function(code, params, acknowledgeCallback)
	{
		//	todo: optionally track acknowledgement and call callback?  maybe eventually.
		var d = new Date();
		
		//rat.console.log("queue request " + code);
		var rEntry = {code:code, params:params, timestamp:d.getTime(), playerID:sync.myPlayerInfo._syncID};
		var ref = sync.requestsRef.push(rEntry);
	},
	
	//	request ownership of a given value from server.
	//	This is so I can do a bunch of changes to it without risking anybody else overwriting it.
	//	The idea here is something like picking a vehicle for my player, or picking up a kefling for my player.
	//	Once the server says this is mine, I can write to it all I want and nobody else will.
	//	todo: on disconnect of owner player (or idle owner player for a few seconds!), host needs to
	//	free up ownership, or we'll get stuck with that thing owned/locked forever.
	sync.requestOwnership = function(key, object, index)
	{
		if (!object && index !== void 0)	//	look up by index
			object = sync.map[key].sourceData[index];
		
		if (object)
			sync.queueRequest('own', {key:key, syncID:object._syncID, playerID:sync.myPlayerInfo._syncID});
	},
	sync.releaseOwnership = function(key, object, index)
	{
		if (!object && index !== void 0)	//	look up by index
			object = sync.map[key].sourceData[index];
		if (object)
		{
			sync.queueRequest('disown', {key:key, syncID:object._syncID, playerID:sync.myPlayerInfo._syncID});
		}
	},
	
	//	event queue handling (host to clients, mostly...)
	sync.queueEvent = function(code, params)
	{
		var d = new Date();
		
		var eventEntry = {code:code, params:params, timestamp:d.getTime()};
		var ref = sync.eventsRef.push(eventEntry);
		
		//rat.console.log("Q -> " + ref.key + code);	//	will print after it's received, on server.
	},
	
	//	chat handling
	sync.queueChat = function(text, code, params)
	{
		if (!code)
			code = 'chat';	//	could be something else, like action or join/leave or something, I dunno...
		if (!params)
			params = null;
		
		var d = new Date();
		
		//	include player name because later somebody might look at this after the player disconnects
		var cEntry = {text:text, code:code, params:params, timestamp:d.getTime(),
			playerID:sync.myPlayerInfo._syncID, playerName:sync.myPlayerInfo.name};
		var ref = sync.chatRef.push(cEntry);
	},
	
	//	Here's a simple way to watch for changes in an object by name,
	//	but maybe better to pass a callback to register function above...
	sync.watchObject = function(key, callback)
	{
		var oRef = sync.mRef.child(key);
		oRef.on('value', function(snap) {
			callback(key, snap.val());
		});
	};
	
	/*
		this is a pain - off works on the reference we created above.
		so, we have to keep that around, I guess.
		maintain a list here?  or, better, a hash?
		remember this is a simple case and we can make assumptions like the game won't have
		two watchers for the very same key.
		
	sync.unwatchObject = function(key)
	{
		var oRef = sync.mRef.child(key);
		oRef.off();
		oRef.update(newData);
	};
	*/
	
});
//--------------------------------------------------------------------------------------------------
//
//	Video player wrapper
//
rat.modules.add( "rat.graphics.r_video",
[
	{name: "rat.graphics.r_graphics", processBefore: true},
	
	"rat.debug.r_console",
	"rat.os.r_system",
	"rat.os.r_events",
	"rat.math.r_math",
], 
function(rat)
{
	var BUFFERING_CHECK_INTERVAL = 2/10;
	var BUFFERING_VIDEO_VARIANCE = BUFFERING_CHECK_INTERVAL * 0.5;

	var lastVidID = 0;
	function log(txt)
	{
		//rat.console.log("VIDEO: " + txt);
	};

	//	Supported format, in order of preference
	var formats = {
		MP4: { type: "mp4", ext:"mp4" },
		WebM: { type: "webm", ext: "wemb" },
		Ogg: { type: "ogg", ext:"ogg" }
	};
	//	Find out what formats we support
	function querySupportedFormats(tag)
	{
		if (formats.queried)
			return;
		for( var format in formats )
		{
			formats[format].supported = !!tag.canPlayType("video/" + formats[format].type);
			log("Format " + format + ".supported = " + formats[format].supported);
		}
		formats.queried = true;
	};
	//	Get the format for a given extension
	function getFormatFromExt(ext)
	{
		if (ext[0] === ".")
			ext = ext.slice(1);
		if (ext)
		{
			for (var format in formats)
			{
				if (formats[format].ext === ext)
					return formats[format];
			}
		}
		return { supported: false };
	};

	//	Given the following file, and the supported formats, generate the file path to use
	function genFilePath( video, file)
	{
		if (!file)
			return file;
		querySupportedFormats(video.htmlTag);

		//	Find the preferred extension
		var ext;
		for( var key in formats )
		{
			if (formats[key].supported)
			{
				ext = formats[key].ext;
				break;
			}
		}
		if (!ext)
		{
			rat.console.log("Unable to find supported video file format.  Defaulting to .mp4");
			ext = formats.MP4.ext;
		}

		var dotPos = file.lastIndexOf('.');
		if (dotPos < 0)
			dotPos = file.length;
		var extString = file.slice(dotPos);
		var format = getFormatFromExt(extString);
		//console.log("extString " + extString);
		if (!format.supported )
		{
			file = file.slice(0, dotPos) + "." + ext;
		}
		return rat.system.fixPath(file);	//	let rat fix up our path, e.g. if we're in some strange hosted environment
	};

	//	Create a new video
	var Video = function( ops )
	{
		ops = ops || {};
		var file = ops.file;
		var foreground = ops.foreground;
		this._playOnLoad = false;	//	This is set if we want to be playing as soon as possible
		this._pauseOnLoad = false	//	This is set if we want to be playing as soon as possible, but paused
		this._pauseOnDoneBuffering = false; // This is set if we want to pause the video as soon as we are done buffering
		this._isPlaying = false;
		this._isLoading = true;		//	Still in the initial load
		this._isBuffering = false;	//	Buffering after starting playback
		this._isMuted = false;		//	is the video muted
		this.events = {};	//	Key is event type, each event is an array of functions to call
		this._loadingDoneWaitingForBufferedData = false;// After we get the canplaythrough event, make sure that we have buffering data.
		
		this._accredBufferingCheck = 0; // How much time has passed sence our last buffering check
		this._lastBufferingCheckTime = 0; // what was the video time at the last buffering check.
		
		
		//	Add any events defined in ops
		this.runningEvents = [];
		this.addEventListener( 'load', ops.onLoad||ops.onload);
		this.addEventListener( 'destroy', ops.onDestroy ||ops.ondestroy );
		this.addEventListener( 'startbuffering', ops.onStartBuffering || ops.onstartbuffering );
		this.addEventListener( 'endbuffering', ops.onEndBuffering || ops.onendbuffering );
		this.addEventListener( 'end', ops.onEnd || ops.onend );
		
		//	NOTE: The video should never be loading and buffering...
		this._isForeground = foreground || false;
		this.htmlTagID = (++lastVidID);
		this.mSrc = "";
		this.mVolume = 1;
		this.createHTMLTag();
		this.setFile(file || "");

		this.buffered = []; // What times were buffered last update.
		
		if (ops.volume !== void 0)
			this.setVolume(ops.volume);
	};
	Video.prototype.disabled = false;
	Video.numberOfActiveBGVideos = 0;
	
	//	Add a new event hanlder = function()
	Video.prototype.addEventListener = function( type, cb, context )
	{
		if (!type || !cb)
			return;
		if (!this.events[type])
			this.events[type] = [];
		this.events[type].push( {cb: cb, context:context} );
	};
	
	//	Remove a registered event handler
	Video.prototype.removeEventListener = function( type, cb, context )
	{
		if (!type || !cb || !this.events[type])
			return;
		var list = this.events[type];
		var entry;
		var running;
		for( var index=0; index < list.length; ++index )
		{
			entry = list[index];
			if (entry.cb === cb && entry.context === context)
			{
				//	Make removing listeners while firing events safe
				for( var rIndex = 0; rIndex < this.runningEvents.length; ++rIndex )
				{
					running = this.runningEvents[rIndex];
					if (running.type == type)
					{
						if (running.onIndex >= rIndex)
							--running.onIndex;
						--running.endIndex;
					}
				}
				list.splice( index, 1 );
				break;
			}
		}
		if (list.length <= 0)
			this.events[type] = void 0;
	};
	
	//	Fire off an events
	Video.prototype.fireEvent = function( type, varArgs )
	{
		if (!type || !this.events[type])
			return;

		//	Same args to all cbs
		//	First param is ALWAYS the video.
		var args = Array.prototype.slice.call(arguments);
		args.unshift(this);
		log( "Video firing event " + type );
		var list = this.events[type];
		var running = {
			type: type,
			onIndex: 0,
			endIndex: list.length-1
		};
		this.runningEvents.push(running);
		var entry;
		for( ;running.onIndex <= running.endIndex; ++running.onIndex )
		{
			entry = list[running.onIndex];
			//	First param is ALWAYS the video, second param is always type.
			entry.cb.apply( entry.context, args );
		}
		this.runningEvents.pop();
	};

	//	State query
	Video.prototype.isPlaying = function ()
	{ 
		if (this.isLoading())
			return this._playOnLoad;
		else
		{
			return this._isPlaying;
		}
	};
	
	Video.prototype.isPaused = function ()
	{
		if (!this.isPlaying())
			return false;
		if (this.isLoading())
			return this._pauseOnLoad;
		else if (this.isBuffering())
			return this._pauseOnDoneBuffering;
		else
			return this.htmlTag.paused;
	};

	Video.prototype.isLoading= function ()
	{ return this._isLoading; };

	Video.prototype.isBuffering = function ()
	{ return this._isBuffering; };

	//	Cleanup this video
	Video.prototype.destroy = function()
	{
		this.fireEvent( "destroy" );
		this.stop();

		//	Trick to get the browser to stop downloading the media
		this.htmlTag.pause();
		this.htmlTag.src = "";
		this.htmlTag.parentNode.removeChild(this.htmlTag);
		this.htmlTag = void 0;
	};

	//	Get the file
	Video.prototype.getFile = function()
	{
		return this.mSrc;
	};
	
	//	Set the source
	Video.prototype.setFile = function (file)
	{
		if (file !== this.mSrc)
		{
			this.mSrc = file;

			//	Get the format provided (if any)
			var fullFile = genFilePath(this, file);
			this.mFullFile = fullFile;
			this.htmlTag.src = this.mFullFile;
			log( "VIDEO source set to " + this.htmlTag.src);
			this._isLoading = true;
			this.htmlTag.load();
		}
		this.stop();
	};
	
	//	Create the html video tag
	Video.prototype.createHTMLTag = function()
	{
		if (this.disabled)
			return;
		
		this.htmlTag = document.createElement("video");
		this.htmlTag.id = this.htmlTagID;
		this.htmlTag.style.position = "absolute";
		var canvas = rat.graphics.canvas;
		canvas.parentNode.appendChild(this.htmlTag);
		if (!canvas.style.zIndex && canvas.style.zIndex !== 0)
			canvas.style.zIndex = 0;
		if (this._isForeground)
			this.htmlTag.style.zIndex = canvas.style.zIndex + (100-(this.htmlTagID));
		else
			this.htmlTag.style.zIndex = canvas.style.zIndex - (100-(this.htmlTagID));
		this.htmlTag.style.backgroundColor = rat.graphics.autoClearColor;
		this.htmlTag.style.objectFit = "contain";

		this.onResize();
		rat.addEventListener("resize", this.onResize.bind(this));
		this.htmlTag.preload = "auto";
		this.htmlTag.oncanplaythrough = this.onLoaded.bind(this);
		//this.htmlTag.oncanplay = this.onLoaded.bind(this);
		this.htmlTag.onended = this.onEnded.bind(this);
		log("created");
	};

	//	Called when the video tag
	Video.prototype.onLoaded = function ()
	{
		if (!this.isLoading())
			return;
		//	The next update will start checking the buffered data.
		this._loadingDoneWaitingForBufferedData = true;
	}

	//	When the video is over
	Video.prototype.onEnded = function ()
	{
		log("Done");
		this.stop();
		this.fireEvent( "end" );
	};

	//	Play the video
	Video.prototype.play = function (file)
	{
		if (file)
			this.setFile(file);
		else if (this.isPlaying())
			return;
		if (!this.mSrc)
		{
			rat.console.log("Attempting to play a video with no source");
			return;
		}

		if (this.isLoading())
		{
			log("->Queue Play");
			this._playOnLoad = true;
		}
		else
		{
			log("->Play");
			this.activateVideo();
			this.htmlTag.play();
			this._isPlaying = true;
		}
	};

	//	Stop the video (it gets hidden)
	Video.prototype.stop = function ()
	{
		if (!this.isPlaying())
			return;

		if (this.isLoading())
		{
			log("->Unqueue play");
			this._playOnLoad = false;
		}
		else
		{
			log("->Stop");
			this.deactivateVideo();
			this.htmlTag.pause();
			this._isPlaying = false;
			if (this.isBuffering())
				this.fireEvent( "endbuffering" );
			this._isBuffering = false;
			this.htmlTag.currentTime = 0;
		}
	};

	//	Pause the video
	Video.prototype.pause = function ()
	{
		if (!this.isPlaying() || this.isPaused())
			return;

		if (this.isLoading())
		{
			log("->Queue Pause");
			this._pauseOnLoad = true;
		}
		//	If we are buffering, don't pause because I won't know when we are done buffering.
		else if (this.isBuffering())
		{
			log("->Queue Pause");
			this._pauseOnDoneBuffering = true;
		}
		else
		{
			this._pauseOnDoneBuffering = false;
			this._pauseOnLoad = false;
			log("->Pause");
			this.htmlTag.pause();
		}
	};
	
	//	Resume the video
	Video.prototype.resume = function ()
	{
		if (!this.isPlaying() || !this.isPaused())
			return;
		if (this.isLoading())
		{
			log("->Unqueue Pause");
			this._pauseOnLoad = false;
		}
		else
		{
			log("->Resume");
			this.htmlTag.play();
		}
	};

	//	Activate a video
	Video.prototype.activateVideo = function ()
	{
		if (!this._isForeground)
			++Video.numberOfActiveBGVideos;
	};

	//	Deactivate a video
	Video.prototype.deactivateVideo = function ()
	{
		if (!this._isForeground)
			--Video.numberOfActiveBGVideos;
	};

	//	Fit the video to canvas.   TODO: Support setting video position/size
	Video.prototype.onResize = function ()
	{
		log("Correcting video element size to match canvas" );
		var canvas = rat.graphics.canvas;
		this.htmlTag.width = canvas.width;
		this.htmlTag.height = canvas.height;
		this.htmlTag.style.left = canvas.style.left;
		this.htmlTag.style.top = canvas.style.top;
	};

	//	Frame-by-frame update
	var UPDATE_EVERY = 0;//(1 / 20);
	Video.prototype.update = function (deltaTime)
	{
		//	Get what pieces of the video we have buffered.  Only do this for videos that have source.
		if (this.mSrc)
		{
			//var buffered = "";
			this.buffered = [];
			if (this.htmlTag.buffered.length)
			{
				for( var i = 0; i < this.htmlTag.buffered.length; ++i )
				{
					var times = {start: this.htmlTag.buffered.start(i), end: this.htmlTag.buffered.end(i)};
					this.buffered.push(times);
					//buffered += times.start + "->" + times.end + " ";
				}
				//rat.console.log( buffered );
			}
		}
		
		//	Have we recived the canplaythrough event but not yet verified that we have buffered data to play?
		if (this._loadingDoneWaitingForBufferedData)
		{
			//	Is there ANY buffered data.
			if (this.htmlTag.buffered.length > 0)
			{
				//	Is the buffered data beyond the 0th second.
				if (this.htmlTag.buffered.end(0) > 0.1)
				{
					//	Yep.  Video is really loaded.
					this._loadingDoneWaitingForBufferedData = false;
					var start = this.htmlTag.buffered.start(0);
					var end = this.htmlTag.buffered.end(0);
					log( this.htmlTagID +  " Loaded.  Initial buffered range is " +start+ " - " +end+ " ." );
					this._isLoading = false;
					this.fireEvent( "load" );
					
					if (this._playOnLoad)
						this.play();
					if (this._pauseOnLoad)
						this.pause();
					this._playOnLoad = false;
					this._pauseOnLoad = false;
				}
				//else
				//	rat.console.log( "Have buffered data... do we have enough? " + this.htmlTag.buffered.end(0));
			}
			//else
			//	rat.console.log( "Looking for buffered data..." );
		}
		else if (!this.isLoading())
			this._updateBuffering(deltaTime);
	};

	//	Update if the video is buffering...
	//	I would prefer to use the onwaiting and onplay events, but for whatever reason, i only seem to get onwaiting once, and then never the play
	var lastReportedTime = void 0;
	Video.prototype._updateBuffering = function (deltaTime)
	{
		//	See http://stackoverflow.com/questions/21399872/how-to-detect-whether-html5-video-has-paused-for-buffering
		this._accredBufferingCheck += deltaTime;
		var nowBuffering = false;
		var currentTime = this.getCurrentTime({useRaw:true});
		if (lastReportedTime !== rat.math.floor(currentTime) )
		{
			lastReportedTime = rat.math.floor(currentTime);
		
			log( "CURTIME: " + lastReportedTime );
		}
			
		//	If the video is paused, don't think we are buffering.
		if (this.htmlTag.paused)
		{
			this._accredBufferingCheck = 0;
			this._lastBufferingCheckTime = this.getCurrentTime({useRaw:true});
			nowBuffering = false;
		}
		//	Is it time to check the buffering again?
		else if (this._accredBufferingCheck > BUFFERING_CHECK_INTERVAL)
		{			
			var last = this._lastBufferingCheckTime;
			var time = this.getCurrentTime({useRaw:true});
			this._lastBufferingCheckTime = time;
			var deltaTime = (time-last);
			nowBuffering = deltaTime < (this._accredBufferingCheck - BUFFERING_VIDEO_VARIANCE);
			this._accredBufferingCheck = 0;
		}
		else
			nowBuffering = this.isBuffering();

		//	If we are in any state were we don't care if we are buffering, then don't update, but 
		//	Also remember to firing the endBuffering event.
		if (this.isLoading() || (this.isPaused() && !this._pauseOnDoneBuffering) || !this.isPlaying())
		{
			//	NOTE: We may be "pause" but only because we are buffering
			//	and a pause was requested
			if (this._pauseOnDoneBuffering)
			{
				//	 Do nothing?
			}
			else
			{
				if (this.isBuffering())
				{
					this.fireEvent( "endbuffering" );
					this._isBuffering = false;
				}
			}
			return;
		}
		
		//rat.console.log( "TB: " + currentTime + "(" + this.isBuffering() + " vs " + nowBuffering + ")" );
		if (this.isBuffering() !== nowBuffering)
		{
			this._isBuffering = nowBuffering;
			if (this.isBuffering() )
				this.fireEvent( "startbuffering" );
			else
			{
				//	Are we suppose to pause now that we are done buffering?
				if (this._pauseOnDoneBuffering)
				{
					this._pauseOnDoneBuffering = false;
					this.pause();
				}
				this.fireEvent( "endbuffering" );
			}
			log((this._isBuffering ? "Is buffering..." : "Is done buffering") +  " at time " + currentTime);
		}
	};

	//	Get how long the video is
	Video.prototype.getDurration = function ()
	{
		if (this.isLoading())
			return -1;
		else
			return this.htmlTag.duration;
	};

	//	Get the current time in the video
	Video.prototype.getCurrentTime = function (ops)
	{
		//	Allow code to directly access the HTML Video tag current time.
		if ( !this.isLoading() || (ops && ops.useRaw))
			return this.htmlTag.currentTime;
		else
			return 0;
	};
	
	//	Set the current time in the video
	Video.prototype.setCurrentTime = function (seekToTime)
	{
		if( seekToTime < 0 || seekToTime > this.htmlTag.duration ) 
		{
			rat.console.log( "Unable to seek to invalid time " + seekToTime );
			return;
		}
		if ( !this.isLoading() )
		{
			log( "Seeking to time " + seekToTime + " while loading..." );
			if (!this.isBuffering())
			{
				this._isBuffering = true;
				if (this.isBuffering() )
					this.fireEvent( "startbuffering" );
				this._lastBufferingCheckTime = seekToTime;
				this._accredBufferingCheck = 0;
			}

			this.htmlTag.currentTime = seekToTime;
		}
		else
			rat.console.log( "Unable to seek to time " + seekToTime + " while loading..." );
	};

	//	Set/get the video volume
	Video.prototype.setVolume = function (vol)
	{
		if (vol < 0)
			vol = 0;
		else if (vol > 1)
			vol = 1;
		if (this.getVolume() !== vol)
		{
			this.mVolume = vol;
			if (!this.isMuted())
				this.htmlTag.volume = vol;
		}
	};

	//	Get the current volume
	Video.prototype.getVolume = function ()
	{
		return this.mVolume;
	};

	//	Get if the video is muted
	Video.prototype.isMuted = function ()
	{
		return this._isMuted;
	};

	//	Mute/unmute the video
	Video.prototype.mute = function (shouldMute)
	{
		if (shouldMute === void 0)
			shouldMute = true;
		if (shouldMute === this.isMuted())
			return;
		this._isMuted = shouldMute;
		if (this.isMuted())
			this.htmlTag.volume = 0;
		else
			this.htmlTag.volume = this.mVolume;
	};
	
	//	We don't support this module (yet) under wraith
	if (rat.system.has.Wraith)
	{
		rat.console.log("r_video not yet ported for wraith support!");
		Video.prototype.disabled = true;
	}

	rat.graphics.Video = Video;
} );

//--------------------------------------------------------------------------------------------------
//
//	User management and information collection for Kongregate API
//	Note that we aggressively fake this data when we're not actually running inside Kongregate,
//	for ease of development.
//
rat.modules.add( "rat.os.r_user_kong",
[
	{ name: "rat.os.r_user", processBefore: true},
	{ name: "rat.os.r_system", processBefore: true},
	{ name: "rat.os.r_kong"},
	
	"rat.debug.r_console",
], 
function(rat)
{
	rat.user = rat.user || {};

	rat.user.supported = true;
	
	//	utility to get the right kind of information request built...
	function makeRequestObject(reqString)
	{
		var rkong = rat.system.kong;
		var xhttp;
		if (window.XMLHttpRequest)
			xhttp = new XMLHttpRequest();
		else
			xhttp = new ActiveXObject("Microsoft.XMLHTTP");
		
		var fake = false;
		if (!rkong || rkong.fake || !rkong.isReady())
			fake = true;
		
		var path = reqString;
		
		//	Cross origin crap.  :(
		//	During development, we want to query user information from kongregate even when we're running locally,
		//	and we run into security errors.  So we need to use CORS, but kongregate doesn't support it.
		//	So, use a proxy.
		//	For a while, cors.io proxy worked.
		//	Switching to https://crossorigin.me/ now.
		
		//	That's not working now, either.
		//	In some cases, serve a file locally.
		
		if (fake)
		{
			//	can we serve any of this locally?
			//var place = reqString.search("username=")
			//if (0)//place >= 0)
			//{
				//path = "fake_data/user_info_gostay.json";
				
			//} else
			{
				xhttp.crossOrigin = "Anonymous";
				
				//	custom solution:
				//	http://104.236.146.97:9186/cdn4.kongcdn.com/assets/kongpanion_icons/0000/0231/blargh.png
				var rOffset = reqString.indexOf("://");
				reqString = path.substring(rOffset+3);
				
				path = "http://104.236.146.97:9186/" + reqString;
				//console.log(path);
				
				//	crossorigin.me solution:
				//path = "https://crossorigin.me/" + reqString;
				
			
				//	older cors.io solution, which also required escaped ampersand:
				/** /	and use escaped ampersand
				  reqString = reqString.replace(/\&/g, "%26");
				  path = "http:  cors.io/?u=" + reqString;
 */
			}
		}
		
		return {xhttp:xhttp, path:path};
	}
	
	//	start a REST request for user data.
	function requestKongUserInfo(user, callback, customData)
	{
		var rkong = rat.system.kong;
		var pname = user.gamerTag;
		rat.console.log("requesting kong user data for " + pname);
		
		user.userInfoStatus = 'pending';

		var req = makeRequestObject("https://api.kongregate.com/api/user_info.json?username=" + pname + "&friends=true");
		
		req.xhttp.onload = function(e) {callback(req.xhttp, user, customData, e);};
		
		req.xhttp.open("GET", req.path, true);	//	async
		req.xhttp.send();
	};
	
	//	normal handling of user info request
	function handleKongUserInfo(xhttp, user, customData, e)
	{
		//	when I get the file locally, this status is 0...  Fix this.
		if (xhttp.readyState === 4 && xhttp.responseText) //&& xhttp.status  === 200)
		{
			rat.console.log("got kong user data");
			var obj = JSON.parse(xhttp.responseText);
			if (obj.success)
			{
				user.kongUserInfo = obj;
				user.userInfoStatus = 'ready';
				
				user.userImageSource = obj.user_vars.avatar_url;
				user.friendsList = [];
				for (var i = 0; i < obj.friends.length; i++)
				{
					user.friendsList.push({
						//	note: this username field needs to be named the same as the user's name,
						//	so we can use this object in similar functions...
						//	I wish we weren't using "gamerTag" as a standard username variable name...
						gamerTag : obj.friends[i],
						id : obj.friend_ids[i],
						userInfo : null,
					});
				}
				
				//	info is available now.  broadcast that it changed.
				rat.user.messages.broadcast(rat.user.messageType.InfoChange, user);
			}
			
		} else {
			user.userInfoStatus = 'error';
		}
	
	};
	
	/**  Get the list of all system users, which on Kongregate is just the current user.
	  * @suppress {missingProperties} */
	rat.user.getUsers = function ()
	{
		var list = new rat.user._internal.UserList();
		var userFields = {};
		var rkong = rat.system.kong;
		if (rkong && rkong.kongregate)
		{
			userFields.id = rkong.kongregate.services.getUserId();
			userFields.gamerTag = rkong.kongregate.services.getUsername();
			
			userFields.isGuest = rkong.kongregate.services.isGuest();
			
			userFields.isSignedIn = true;
			userFields.userImageSource = null;	//	todo
			userFields.friendsList = null;	//	todo
			
		} else {
			//	fake user data
			
			userFields.id = -1;
			//	test with gostay, generally.
			//	other test names:
			//		weeozy has 355 friends, zeeg has 20, aquagamer 76, jimgreer has 500 (max)
			userFields.gamerTag = "gostay";
			userFields.isGuest = false;
			userFields.isSignedIn = true;
			userFields.userImageSource = null;	//	todo
			userFields.friendsList = null;	//	todo
		}
		var user = list.add(userFields, userFields);	//	use userfields as raw data and as setupdata both...
		
		//	start a request for some user info
		//	STT this is failing - disabled until we know why.
		//requestKongUserInfo(user, handleKongUserInfo);

		return list;
	};

	/** @suppress {missingProperties} */
	//	Will we need something like this?  We might.
	/*
	var kongSigninChanged = function (type, eventArgs)
	{
		var user = eventArgs.user;
		if (type === rat.user.messageType.SignOut && rat.user.getActiveUser() && user.someKongUniqueID === rat.user.getActiveUserID())
			rat.user.clearActiveUser();
		rat.user.messages.broadcast(type, user.someKongUniqueID);
	};
	*/

	/**  Seems like this should be in r_input except that it is all about the user binding to a controller changing...
	  * @suppress {missingProperties} */
	/*
	//	This, too...
	function addListeners()
	{
		//	something about listening to kongregate when signin changes?
	}
	addListeners();
	*/

	//	except the actual kong API is not available until a callback is called, some time after startup.
	//	so, in pracice, we can't do much here.
	//function defaultKongUserSetup()
	//{
	//}
	//defaultKongUserSetup();
	
	//	request details for all friends
	//	Kongregate specifically has an API for requesting multiple user's info at once...
	//	So, we'll use that.  Oddly, it only supports 50 at a time, so we have to break up our requests.
	rat.user.requestFriendsInfo = function(user)
	{
		if (!user.friendsList)
			return;
		
		rat.console.log("requesting info for " + user.friendsList.length + " friends");
		
		var requestList = [];
		for (var i = 0; i < user.friendsList.length; i++)
		{
			user.friendsList[i].userInfoStatus = 'pending';
			requestList.push(user.friendsList[i].id);
			if (requestList.length >= 50)
			{
				requestFriendGroup(user, requestList);
				requestList = [];
			}
		}
		if (requestList.length)
			requestFriendGroup(user, requestList);
	};
	
	//	make the actual request for a group of ids
	function requestFriendGroup(user, list)
	{
		rat.console.log("requestFriendGroup count " + list.length);
		
		var reqString = "https://api.kongregate.com/api/user_info.json?user_ids=";
		for (var i = 0; i < list.length; i++)
		{
			reqString += "" + list[i];
			if (i < list.length-1)
				reqString += ",";
		}
		
		var req = makeRequestObject(reqString);
		
		req.xhttp.onload = function(e) {handleFriendInfo(req.xhttp, user, null, e);};
		
		req.xhttp.open("GET", req.path, true);	//	async
		req.xhttp.send();
	};
	
	//	handling of user info results for a group of friends
	function handleFriendInfo(xhttp, origUser, customData, e)
	{
		if (xhttp.readyState === 4 && xhttp.status  === 200)
		{
			rat.console.log("got kong friend data");
			var obj = JSON.parse(xhttp.responseText);
			
			//	match up the results one by one
			for (var i = 0; i < obj.users.length; i++)
			{
				for (var fIndex = 0; fIndex < origUser.friendsList.length; fIndex++)
				{
					if (obj.users[i].user_id === origUser.friendsList[fIndex].id)
					{
						origUser.friendsList[fIndex].userInfo = obj.users[i];
						origUser.friendsList[fIndex].userInfoStatus = 'ready';
						origUser.friendsList[fIndex].userImageSource = obj.users[i].user_vars.avatar_url;
					}
				}
			}
			
		} else {
			//user.userInfoStatus = 'error';
		}
		
		for (var fIndex = 0; fIndex < origUser.friendsList.length; fIndex++)
		{
			if (origUser.friendsList[fIndex].userInfoStatus !== 'ready')
				return;
		}
		
		//	info is available now.  broadcast that it changed.
		rat.user.messages.broadcast(rat.user.messageType.FriendsInfoAvailable, origUser);
	};
	
	//	Init user access, once it's available.
	//	So, we expect this function to get called at some point after kong API is actually hooked up,
	//	some time after startup.
	rat.user.init = function(rkong)
	{
		rat.console.log("rat.user.init kong");
		
		var localUserID = rkong.getUserId();
		rat.user.setActiveUser(localUserID);
		
		//	todo: (though, maybe do these on setActiveUser?)
		//	start user info query.
		//	start kongpanion list query (optional?)
		//	start friend list query (optional?)
		//	start user picture query? (optional?)
	};
	
	//	write stat for the active user (which is all kong supports anyway)
	rat.user.writeStat = function(statName, value)
	{
		var rkong = rat.system.kong;
		if (rkong && !rkong.fake && rkong.isReady())
		{
			rkong.kongregate.stats.submit(statName, value);
		} else {
			rat.console.log("non-kong stat write attemp:  " + statName + " : " + value);
		}
	};


} );
//--------------------------------------------------------------------------------------------------
//
//	Rat timers.  A timer calls a provided function after the specified time.
//	USAGE:
//
//		var myTimer = new rat.timer(function(theTimer, theParam) {...}, 3, "myCustomData");
//		myTimer.autoUpdate();
//
//	This "autoUpdate()" is interesting.  It'd be easier if that happened by default.
//		and you could optionally pass an argument saying you don't want that?
//		how often do we not want timers to automatically update?
//		maybe we need a simple "makeTimer" function that does it automatically?
//
/** 	@todo: make autoUpdate the default, and support another argument suppressing that, since that's the exception.
  
  	@todo: since this is a constructable object, should use capital "Timer"
 */

//------------ rat.cycleUpdate ----------------
rat.modules.add( "rat.utils.r_timer",
[
	"rat.debug.r_console",
], 
function(rat)
{
	/**
	 * A timer that calls the function at the specified delay.
	 * @param {function(rat.timer, ?)} func the function to call. The first
	 *	argument is the timer that went off. The second is param2 provided here.
	 * @param {number} delay when, from now, will the function be called.
	 * @param {?} param2 the second parameter of the function to call.
	 * @constructor
	 */
	rat.timer = function(func, delay, param2)
	{
		this.func = func;
		this.param2 = param2;
		this.currentTime = 0;
		this.delay = delay;
		this.flags = 0;
	};
	
	/**
	 * Sets a function to be called after the delay.
	 * @param {function(rat.timer, ?)} func the function to call. The first
	 *	argument is the timer that went off. The second is param2 provided here.
	 * @param {number} delay when, from now, will the function be called.
	 * @param {?} param2 the second parameter of the function to call.
	 * @return {rat.timer} timer to handle the delayed call.
	 */
	rat.setTimeout = function(func, delay, param2)
	{
		var timer = new rat.timer(func, delay, param2);
		timer.autoUpdate();
		return timer;
	};
	
	/**
	 * Sets a function to be called repeatedly with a fixed time between calls.
	 * @param {function(rat.timer, ?)} func the function to call. The first
	 *	argument is the timer that went off. The second is param2 provided here.
	 * @param {number} delay fixed time between calls.
	 * @param {?} param2 the second parameter of the function to call.
	 * @return {rat.timer} timer to handle the calls.
	 */
	rat.setInterval = function(func, delay, param2)
	{
		var timer = new rat.timer(func, delay, param2);
		timer.setRepeat(true);
		timer.autoUpdate();
		return timer;
	};
	
	/**
	 * Flag constants
	 */
	var FLAG_REPEAT = 0x0001; // Tells the timer to repeate.
	var FLAG_PAUSED = 0x0002; // Pauses the timer.
	var FLAG_FIRED = 0x4000; // Marks that the timer has fired.
	var FLAG_DEAD = 0x8000; // Marks that the timer to be removed from auto updating.
	
	/**
	 * Sets if the timer should repeate.
	 * @param {boolean} repeat
	 */
	rat.timer.prototype.setRepeat = function(repeat)
	{
		if(repeat)
			this.flags |= FLAG_REPEAT;
		else
			this.flags &= ~FLAG_REPEAT;
	};
	
	/**
	 * Sets if the timer is paused.
	 * @param {boolean} pause
	 */
	rat.timer.prototype.setPause = function(pause)
	{
		if(pause)
			this.flags |= FLAG_PAUSED;
		else
			this.flags &= ~FLAG_PAUSED;
	};
	
	/**
	 * Updates the timer's time.
	 * @param {number} deltaTime
	 */
	rat.timer.prototype.update = function(deltaTime)
	{
		if(!(this.flags & (FLAG_PAUSED | FLAG_FIRED | FLAG_DEAD)))
		{
			this.currentTime += deltaTime;
			if(this.currentTime >= this.delay)
				this.callNow();
		}
	};
	
	/**
	 * Sets the timer to automatically update.
	 * These timers are not guaranteed to update in any order.
	 */
	rat.timer.prototype.autoUpdate = function()
	{
		// If we are adding a dead timer to automatically update, I think the
		// caller wants it to be called again.
		this.flags &= ~FLAG_DEAD;
		this.flags &= ~FLAG_FIRED;
		
		rat.timerUpdater.timers.push(this);
	};
	
	/**
	 * Sets the timer to stop automatically updating.
	 * (marks dead - will get removed from autoupdate list when that list is next processed)
	 * It's OK to call this from within the timer's update function.
	 */
	rat.timer.prototype.endAutoUpdate = function()
	{
		
		this.flags |= FLAG_DEAD;
	};
	
	/**
	 * Sets off the timer now to run the function.
	 */
	rat.timer.prototype.callNow = function()
	{
		this.func(this, this.param2);
		
		if(this.flags & FLAG_REPEAT)
			this.currentTime = 0;
		else
		{
			this.flags |= FLAG_DEAD;
			this.flags |= FLAG_FIRED;
		}
	};
	
	/**
	 * Tracks which timers to automatically update.
	 */
	rat.timerUpdater = {
		/**
		 * @type {Array.<rat.timer>} Holds the timers to automatically update.
		 */
		timers: [],
	};
	
	/**
	 * Run all of the auto-updating timers.
	 * @param {number} deltaTime
	 */
	rat.timerUpdater.updateAll = function (deltaTime)
	{
		for(var index = this.timers.length - 1; index >= 0; --index)
		{
			var timer = this.timers[index];
			
			//	Update timer.  Note that paused/dead timers don't really update (see update function)
			//	STT note: maybe do that check here in case somebody overrides update function?
			timer.update(deltaTime);
			
			//	if the timer is now dead (e.g. from the update step, or something else)
			//	then remove it from our update list.
			if(timer.flags & FLAG_DEAD)
			{
				// remove the dead timer by switching it with the one at the
				// end of the list and popping.
				this.timers[index] = this.timers[this.timers.length - 1];
				this.timers.pop();
			}
		}
	};
	
	/**
	 * Tests the timers.
	 */
	rat.timerUpdater.test = function()
	{
		rat.setTimeout(function(){ rat.console.log("2 seconds once"); }, 2);
		rat.setInterval(function(timer, string){ rat.console.log(string); }, 3, "Every 3 seconds.");
	};
} );
//--------------------------------------------------------------------------------------------------
//
//	Clipboard emulation or access, depending on the system.
//
//	Usage:  clipboard.store({type:'text', value:"My text"});
//			var myValue = clipboard.retrieve('text');
//
//			the idea here is that the clipboard's data could be interpreted several ways, e.g. text, image, path, structure...
//
//	This could eventually do a bunch of work to try to access clipboard,
//	and could detect chrome app status and behave differently.
//	For now, all it does is implement a clipboard local to this webpage.
//
//	By default, we use localstore to cache clipboard across sessions, which is really convenient.
//
rat.modules.add( "rat.os.r_clipboard",
[
	"rat.storage.r_storage",
],
function(rat)
{
	var clipboard = {
		blobs : {
			//	hash, with type as key (and type also listed in object)
			//	e.g.:
			//	'text' : {type:'text', value:'hello'}
		},
	};
	
	clipboard.init = function ()
	{
		//	use a userID here to avoid problems with prefix changing when the app decides it wants to use storage.
		clipboard.localSave = rat.storage.getStorage(rat.storage.permanentLocal, 'rat');
		if (clipboard.localSave)
		{
			//	load anything that was already there.  This is the key value of the cache - to start up with content already.
			var data = clipboard.localSave.getObject('rat_clipboard');
			if (data && data.version === 2)
			{
				clipboard.blobs = data.blobs;
			}
		}
	};
	
	clipboard.cache = function()
	{
		if (clipboard.localSave)
			clipboard.localSave.setObject('rat_clipboard',
			{
				version:2,
				blobs:clipboard.blobs,
			});
	};
	
	//	store current value with this type
	//	{type, value} or array of them.
	clipboard.store = function (blobs, arg2)
	{
		//	old simpler format:  type and value.
		if (typeof(blobs) === 'string')
		{
			blobs = {type:blobs, value:arg2};
		}
		if (!Array.isArray(blobs))
			blobs = [blobs];
		
		clipboard.blobs = {};
		for (var i = 0; i < blobs.length; i++)
		{
			var blob = blobs[i];
			//	store in hash with type as key 
			clipboard.blobs[blob.type] = {type:blob.type, value:blob.value};
		}
		
		clipboard.cache();
	};
	
	//	return value of this type, if it's here.
	//	otherwise return null.
	clipboard.retrieve = function (type)
	{
		if (clipboard.blobs[type])
			return clipboard.blobs[type].value;
		else
			return null;
	};
	
	//	return whatever data we have!
	clipboard.getData = function ()
	{
		if (clipboard.blobs)
			return clipboard.blobs;
		else
			return null;
	};
	
	//	global namespace access
	rat.clipboard = clipboard;
	
});
//--------------------------------------------------------------------------------------------------
//
//	Basic elements for all fills
//
rat.modules.add( "rat.ui.r_ui_fill",
[
	{name: "rat.utils.r_utils", processBefore: true },
	{name: "rat.ui.r_ui", processBefore: true },
	"rat.ui.r_ui_data",
], 
function(rat)
{
	/**
	 * @constructor
	 * @extends rat.ui.Element
	*/
	rat.ui.Fill = function ()
	{
		rat.ui.Fill.prototype.parentConstructor.call(this); //	default init
		this.fillCur = 5;
		this.fillTotal = 10;
		this.percent = 0.5;
		this.allowOverFill = false;
		this.allowUnderFill = false;
		this.fillStyle = 'stroke';	/** < Currently supports radialStroke, radialFill, stroke, fill
 */
		this.fillShape = 'circle';		/** < Currently supports circle or rectangle
 */
		this.lineWidth = 1; /**  When using stroke styles
 */
		this.setTracksMouse(false);	//	no mouse tracking, highlight, tooltip, etc. including subelements.
	};
	rat.utils.inheritClassFrom(rat.ui.Fill, rat.ui.Element);
	rat.ui.Fill.prototype.elementType = "fill";
	
	/** 	Set the fill
 */
	rat.ui.Fill.prototype.setFill = function (cur, total)
	{
		if (this.fillCur !== cur || (total !== void 0 && this.fillTotal !== total) )
		{
			this.fillCur = cur;
			if(total !== void 0)
				this.fillTotal = total;
				
			if (!this.allowUnderFill && this.fillCur < 0)
				this.fillCur = 0;
			if (!this.allowOverFill && this.fillCur > this.fillTotal)
				this.fillCur = this.fillTotal;
			this.percent = this.fillCur/this.fillTotal;
			this.setDirty(true);
		}
	};

	// Support for creation from data
	//
	//maxFill,
	//currentFill,
	//allowOverFill,
	//allowUnderFill,
	//fillStyle,
	//fillShape,
	//lineWidth
	//}
	//
	rat.ui.Fill.setupFromData = function (pane, data, parentBounds)
	{
		rat.ui.data.callParentSetupFromData(rat.ui.Fill, pane, data, parentBounds);
		if (data.allowOverFill)
			pane.allowOverFill = data.allowOverFill;
		if (data.allowUnderFill)
			pane.allowUnderFill = data.allowUnderFill;
		pane.setFill(data.currentFill || 0, data.maxFill);
		pane.fillStyle = data.fillStyle || pane.fillStyle;
		pane.fillShape = data.fillShape || pane.fillShape;
		if (data.lineWidth !== void 0)
			pane.lineWidth = data.lineWidth;
	};
	
});
//--------------------------------------------------------------------------------------------------
//
//	rat_load_now
//
//	Used with concatenated rat.js to force loading of the engine NOW
//
rat.load({skipLoad:true, async:false});


//	revision info, generated automatically (through templates below) during concat/compile build steps
rat.versionInfo = {};
rat.versionInfo.version ="8516";
rat.versionInfo.localMods = "";
rat.versionInfo.buildDate = "2017/04/04 17:29:37";