diff --git a/indra/llcommon/indra_constants.h b/indra/llcommon/indra_constants.h
index a0b129363929a571f9b578a8871f1f2689eab964..5697fb9f41d6a9d0bcb45886d7bc7cd053233a39 100644
--- a/indra/llcommon/indra_constants.h
+++ b/indra/llcommon/indra_constants.h
@@ -38,7 +38,11 @@
 // At 45 Hz collisions seem stable and objects seem
 // to settle down at a reasonable rate.
 // JC 3/18/2003
-const F32 PHYSICS_TIMESTEP = 1.f / 45.f;
+
+// const F32 PHYSICS_TIMESTEP = 1.f / 45.f;
+// This must be a #define due to anal retentive restrictions on const expressions
+// CG 2008-06-05
+#define PHYSICS_TIMESTEP (1.f / 45.f)
 
 const F32 COLLISION_TOLERANCE = 0.1f;
 const F32 HALF_COLLISION_TOLERANCE = COLLISION_TOLERANCE * 0.5f;
@@ -50,9 +54,9 @@ const U32 DAYS_PER_LINDEN_YEAR		= 11;
 const U32 SEC_PER_LINDEN_DAY		= HOURS_PER_LINDEN_DAY * 60 * 60;
 const U32 SEC_PER_LINDEN_YEAR		= DAYS_PER_LINDEN_YEAR * SEC_PER_LINDEN_DAY;
 
-const F32 REGION_WIDTH_METERS = 256.f;
-const S32 REGION_WIDTH_UNITS = 256;
-const U32 REGION_WIDTH_U32 = 256;
+static const F32 REGION_WIDTH_METERS = 256.f;
+static const S32 REGION_WIDTH_UNITS = 256;
+static const U32 REGION_WIDTH_U32 = 256;
 
 const F32 REGION_HEIGHT_METERS = 4096.f;
 
diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp
index 2c7c7e38a4885fd61b6e3ef347af75cf54f2edfc..88262516178b03a8de6af73b15f6618424e3e788 100644
--- a/indra/llcommon/llapp.cpp
+++ b/indra/llcommon/llapp.cpp
@@ -224,6 +224,7 @@ LLSD LLApp::getOptionData(OptionPriority level)
 void LLApp::stepFrame()
 {
 	LLFrameTimer::updateFrameTime();
+	LLFrameTimer::updateFrameCount();
 	LLEventTimer::updateClass();
 	mRunner.run();
 }
diff --git a/indra/llcommon/llframetimer.cpp b/indra/llcommon/llframetimer.cpp
index b74151bc2a443a39ba0cbd58ef73f64cdaa1ae04..a61c5bda81d9d6fb9e01129682a0de6ec9ab3663 100644
--- a/indra/llcommon/llframetimer.cpp
+++ b/indra/llcommon/llframetimer.cpp
@@ -53,7 +53,6 @@ void LLFrameTimer::updateFrameTime()
 	sTotalTime = total_time;
 	sTotalSeconds = U64_to_F64(sTotalTime) * USEC_TO_SEC_F64;
 	sFrameTime = U64_to_F64(sTotalTime - sStartTotalTime) * USEC_TO_SEC_F64;
-	sFrameCount++;
 } 
 
 void LLFrameTimer::start()
diff --git a/indra/llcommon/llframetimer.h b/indra/llcommon/llframetimer.h
index 2998560ab9fe43b391c6fb707b8db556971c21cc..7ef92f64e4116d73bbaa2842180f6fc7041002cc 100644
--- a/indra/llcommon/llframetimer.h
+++ b/indra/llcommon/llframetimer.h
@@ -67,10 +67,14 @@ class LLFrameTimer
 		return sTotalSeconds;
 	}
 
-	// Call this method once per frame to update the current frame time.
+	// Call this method once per frame to update the current frame time.   This is actually called
+	// at some other times as well
 	static void updateFrameTime();
 
-	static S32  getFrameCount()						{ return sFrameCount; }
+	// Call this method once, and only once, per frame to update the current frame count.
+	static void updateFrameCount()					{ sFrameCount++; }
+
+	static U32  getFrameCount()						{ return sFrameCount; }
 
 	static F32	getFrameDeltaTimeF32();
 
diff --git a/indra/llcommon/llkeythrottle.h b/indra/llcommon/llkeythrottle.h
index d831eadd59ea6faebb319913022fbfa15e006025..61a43b2dadb13593dcbc170607ef2a8fac19f14f 100644
--- a/indra/llcommon/llkeythrottle.h
+++ b/indra/llcommon/llkeythrottle.h
@@ -68,20 +68,24 @@ class LLKeyThrottleImpl
 	U32 countLimit;
 		// maximum number of keys allowed per interval
 		
-	U64 interval_usec;
-		// each map covers this time period
-	U64 start_usec;
+	U64 intervalLength;		// each map covers this time period (usec or frame number)
+	U64 startTime;			// start of the time period (usec or frame number)
+	
 		// currMap started counting at this time
 		// prevMap covers the previous interval
 	
 	LLKeyThrottleImpl() : prevMap(0), currMap(0),
-			      countLimit(0), interval_usec(0),
-			      start_usec(0) { };
+			      countLimit(0), intervalLength(1),
+			      startTime(0) { };
 
 	static U64 getTime()
 	{
 		return LLFrameTimer::getTotalTime();
 	}
+	static U64 getFrame()		// Return the current frame number
+	{
+		return (U64) LLFrameTimer::getFrameCount();
+	}
 };
 
 
@@ -89,17 +93,10 @@ template< class T >
 class LLKeyThrottle
 {
 public:
-	LLKeyThrottle(U32 limit, F32 interval)
+	LLKeyThrottle(U32 limit, F32 interval, BOOL realtime = TRUE)	// realtime = FALSE for frame-based throttle, TRUE for usec real-time throttle
 		: m(* new LLKeyThrottleImpl<T>)
 	{
-		// limit is the maximum number of keys
-		// allowed per interval (in seconds)
-		m.countLimit = limit;
-		m.interval_usec = (U64)(interval * USEC_PER_SEC);
-		m.start_usec = LLKeyThrottleImpl<T>::getTime();
-
-		m.prevMap = new typename LLKeyThrottleImpl<T>::EntryMap;
-		m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap;
+		setParameters( limit, interval, realtime );
 	}
 		
 	~LLKeyThrottle()
@@ -118,18 +115,26 @@ class LLKeyThrottle
 	// call each time the key wants use
 	State noteAction(const T& id, S32 weight = 1)
 	{
-		U64 now = LLKeyThrottleImpl<T>::getTime();
+		U64 now = 0;
+		if ( mIsRealtime )
+		{
+			now = LLKeyThrottleImpl<T>::getTime();
+		}
+		else
+		{
+			now = LLKeyThrottleImpl<T>::getFrame();
+		}
 
-		if (now >= (m.start_usec + m.interval_usec))
+		if (now >= (m.startTime + m.intervalLength))
 		{
-			if (now < (m.start_usec + 2 * m.interval_usec))
+			if (now < (m.startTime + 2 * m.intervalLength))
 			{
 				// prune old data
 				delete m.prevMap;
 				m.prevMap = m.currMap;
 				m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap;
 
-				m.start_usec += m.interval_usec;
+				m.startTime += m.intervalLength;
 			}
 			else
 			{
@@ -139,7 +144,7 @@ class LLKeyThrottle
 				m.prevMap = new typename LLKeyThrottleImpl<T>::EntryMap;
 				m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap;
 
-				m.start_usec = now;
+				m.startTime = now;
 			}
 		}
 
@@ -166,7 +171,7 @@ class LLKeyThrottle
 		// (now) time.
 
 		// compute current, windowed rate
-		F64 timeInCurrent = ((F64)(now - m.start_usec) / m.interval_usec);
+		F64 timeInCurrent = ((F64)(now - m.startTime) / m.intervalLength);
 		F64 averageCount = curr.count + prevCount * (1.0 - timeInCurrent);
 		
 		curr.blocked |= averageCount > m.countLimit;
@@ -224,8 +229,46 @@ class LLKeyThrottle
 		return FALSE;
 	}
 
+	// Get the throttling parameters
+	void getParameters( U32 & out_limit, F32 & out_interval, BOOL & out_realtime )
+	{
+		out_limit = m.countLimit;
+		out_interval = m.intervalLength;
+		out_realtime = mIsRealtime;
+	}
+
+	// Set the throttling behavior
+	void setParameters( U32 limit, F32 interval, BOOL realtime )
+	{
+		// limit is the maximum number of keys
+		// allowed per interval (in seconds or frames)
+		mIsRealtime = realtime;
+		m.countLimit = limit;
+		if ( mIsRealtime )
+		{
+			m.intervalLength = (U64)(interval * USEC_PER_SEC);
+			m.startTime = LLKeyThrottleImpl<T>::getTime();
+		}
+		else
+		{
+			m.intervalLength = (U64)interval;
+			m.startTime = LLKeyThrottleImpl<T>::getFrame();
+		}
+
+		if ( m.intervalLength == 0 )
+		{	// Don't allow zero intervals
+			m.intervalLength = 1;
+		}
+
+		delete m.prevMap;
+		m.prevMap = new typename LLKeyThrottleImpl<T>::EntryMap;
+		delete m.currMap;
+		m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap;
+	}
+
 protected:
 	LLKeyThrottleImpl<T>& m;
+	BOOL	mIsRealtime;	// TRUE to be time based (default), FALSE for frame based
 };
 
 #endif
diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp
index 7a2d42dfaa613cd9561a21d813eeb759b8640a7a..3a8756a31fa174bf2ad0edff5e1568919f8ff376 100644
--- a/indra/llcommon/llstring.cpp
+++ b/indra/llcommon/llstring.cpp
@@ -929,6 +929,32 @@ namespace LLStringFn
 			}
 		}
 	}
+
+	// https://wiki.lindenlab.com/wiki/Unicode_Guidelines has details on
+	// allowable code points for XML. Specifically, they are:
+	// 0x09, 0x0a, 0x0d, and 0x20 on up.  JC
+	std::string strip_invalid_xml(const std::string& input)
+	{
+		std::string output;
+		output.reserve( input.size() );
+		std::string::const_iterator it = input.begin();
+		while (it != input.end())
+		{
+			// Must compare as unsigned for >=
+			// Test most likely match first
+			const unsigned char c = (unsigned char)*it;
+			if (   c >= (unsigned char)0x20   // SPACE
+				|| c == (unsigned char)0x09   // TAB
+				|| c == (unsigned char)0x0a   // LINE_FEED
+				|| c == (unsigned char)0x0d ) // CARRIAGE_RETURN
+			{
+				output.push_back(c);
+			}
+			++it;
+		}
+		return output;
+	}
+
 }
 
 
diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h
index 8a1e1fe8cb378808d51655f03c1f43b456f999e7..18cfe4b64cf84c99a3e94c14189ad50ec75cb676 100644
--- a/indra/llcommon/llstring.h
+++ b/indra/llcommon/llstring.h
@@ -545,6 +545,13 @@ namespace LLStringFn
 	 */
 	void replace_nonprintable_and_pipe(std::basic_string<llwchar>& str,
 									   llwchar replacement);
+
+	/**
+	 * @brief Remove all characters that are not allowed in XML 1.0.
+	 * Returns a copy of the string with those characters removed.
+	 * Works with US ASCII and UTF-8 encoded strings.  JC
+	 */
+	std::string strip_invalid_xml(const std::string& input);
 }
 
 ////////////////////////////////////////////////////////////
@@ -739,17 +746,9 @@ LLStringBase<T>::LLStringBase(const T* s, size_type n ) : std::basic_string<T>()
 
 // Init from a substring
 template<class T> 
-LLStringBase<T>::LLStringBase(const T* s, size_type pos, size_type n ) : std::basic_string<T>()
-{
-	if( s )
-	{
-		assign(s + pos, n);
-	}
-	else
-	{
-		assign(LLStringBase<T>::null);
-	}
-}
+LLStringBase<T>::LLStringBase(const T* s, size_type pos, size_type n )
+: std::basic_string<T>( (s ? s : std::basic_string<T>() ), pos, n )
+{ }
 
 //static
 template<class T> 
diff --git a/indra/llcommon/llversionserver.h b/indra/llcommon/llversionserver.h
index 291f6d63472b58b0e0daf494031de70f2d2bed10..97472ea208c382126f4d826f54d9c54136f7513f 100644
--- a/indra/llcommon/llversionserver.h
+++ b/indra/llcommon/llversionserver.h
@@ -33,9 +33,9 @@
 #define LL_LLVERSIONSERVER_H
 
 const S32 LL_VERSION_MAJOR = 1;
-const S32 LL_VERSION_MINOR = 21;
-const S32 LL_VERSION_PATCH = 1;
-const S32 LL_VERSION_BUILD = 86526;
+const S32 LL_VERSION_MINOR = 22;
+const S32 LL_VERSION_PATCH = 2;
+const S32 LL_VERSION_BUILD = 87048;
 
 const char * const LL_CHANNEL = "Second Life Server";
 
diff --git a/indra/llmessage/llservicebuilder.cpp b/indra/llmessage/llservicebuilder.cpp
index d5c601414067ded4d1340e26dbb2400e7392a32e..6442767ebd3bf0f608e5433939db0272b2b380e8 100644
--- a/indra/llmessage/llservicebuilder.cpp
+++ b/indra/llmessage/llservicebuilder.cpp
@@ -111,10 +111,13 @@ std::string LLServiceBuilder::buildServiceURI(
 	const std::string& service_name,
 	const LLSD& option_map)
 {
-	std::string service_url = buildServiceURI(service_name);
-    
-	// Find the Service Name
-	if(!service_url.empty() && option_map.isMap())
+	return russ_format(buildServiceURI(service_name), option_map);
+}
+
+std::string russ_format(const std::string& format_str, const LLSD& context)
+{
+	std::string service_url(format_str);
+	if(!service_url.empty() && context.isMap())
 	{
 		// throw in a ridiculously large limiter to make sure we don't
 		// loop forever with bad input.
@@ -134,9 +137,9 @@ std::string LLServiceBuilder::buildServiceURI(
 			std::string::iterator end(service_url.end());
 			std::string::iterator deepest_node(service_url.end());
 			std::string::iterator deepest_node_end(service_url.end());
-			//parse out the variables to replace by going through {}s one at a time,
-			// starting with the "deepest" in series {{}},
-			// and otherwise replacing right-to-left
+			// parse out the variables to replace by going through {}s
+			// one at a time, starting with the "deepest" in series
+			// {{}}, and otherwise replacing right-to-left
 			for(; iter != end; ++iter)
 			{
 				switch(*iter)
@@ -171,7 +174,7 @@ std::string LLServiceBuilder::buildServiceURI(
 			// params and straight string substitution, so it's a
 			// known distance of 2 to skip the directive.
 			std::string key(deepest_node + 2, deepest_node_end);
-			LLSD value = option_map[key];
+			LLSD value = context[key];
 			switch(*(deepest_node + 1))
 			{
 			case '$':
@@ -184,7 +187,9 @@ std::string LLServiceBuilder::buildServiceURI(
 				}
 				else
 				{
-					llwarns << "Unknown key: " << key << " in option map: " << LLSDOStreamer<LLSDNotationFormatter>(option_map) << llendl;
+					llwarns << "Unknown key: " << key << " in option map: "
+						<< LLSDOStreamer<LLSDNotationFormatter>(context)
+						<< llendl;
 					keep_looping = false;
 				}
 				break;
@@ -212,50 +217,3 @@ std::string LLServiceBuilder::buildServiceURI(
 	}
 	return service_url;
 }
-
-
-
-// Old, not as good implementation. Phoenix 2007-10-15
-#if 0
-		// Do brace replacements - NOT CURRENTLY RECURSIVE
-		for(LLSD::map_const_iterator option_itr = option_map.beginMap();
-			option_itr != option_map.endMap();
-			++option_itr)
-		{
-			std::string variable_name = "{$";
-			variable_name.append((*option_itr).first);
-			variable_name.append("}");
-			std::string::size_type find_pos = service_url.find(variable_name);
-			if(find_pos != std::string::npos)
-			{
-				service_url.replace(
-					find_pos,
-					variable_name.length(),
-					(*option_itr).second.asString());
-				continue;
-			}
-			variable_name.assign("{%");
-			variable_name.append((*option_itr).first);
-			variable_name.append("}");
-			find_pos = service_url.find(variable_name);
-			if(find_pos != std::string::npos)
-			{
-				std::string query_str = LLURI::mapToQueryString(
-					(*option_itr).second);
-				service_url.replace(
-					find_pos,
-					variable_name.length(),
-					query_str);
-			}
-		}
-	}
-
-	if (service_url.find('{') != std::string::npos)
-	{
-		llwarns << "Constructed a likely bogus service URL: " << service_url
-				<< llendl;
-	}
-
-	return service_url;
-}
-#endif
diff --git a/indra/llmessage/llservicebuilder.h b/indra/llmessage/llservicebuilder.h
index 2c006608931a5bfea7a5f712e0f4728c58d4e239..30e44923b67142f8c98f2fec49f9c0c987461c38 100644
--- a/indra/llmessage/llservicebuilder.h
+++ b/indra/llmessage/llservicebuilder.h
@@ -38,11 +38,24 @@
 
 class LLSD;
 
+/**
+ * @brief  Format format string according to rules for RUSS.
+ *
+ * This function appears alongside the service builder since the
+ * algorithm was originally implemented there. This can eventually be
+ * moved when someone wants to take the time.
+ * @see https://osiris.lindenlab.com/mediawiki/index.php/Recursive_URL_Substitution_Syntax
+ * @param format_str The input string to format.
+ * @param context A map used for string substitutions.
+ * @return Returns the formatted string. If no match is found for a
+ * substitution target, the braces remain intact.
+ */
+std::string russ_format(const std::string& format_str, const LLSD& context);
+
 /** 
  * @class LLServiceBuilder
  * @brief This class builds urls for us to use when making web service calls.
  */
-
 class LLServiceBuilder
 {
 	LOG_CLASS(LLServiceBuilder);
diff --git a/indra/llui/llbutton.cpp b/indra/llui/llbutton.cpp
index b8cf8f7984e6f0062579c998acc7e602f2cf2c70..7d54aeb95003e0928970034de7e760e90f3a35c2 100644
--- a/indra/llui/llbutton.cpp
+++ b/indra/llui/llbutton.cpp
@@ -321,7 +321,7 @@ BOOL LLButton::handleMouseDown(S32 x, S32 y, MASK mask)
 	}
 
 	mMouseDownTimer.start();
-	mMouseDownFrame = LLFrameTimer::getFrameCount();
+	mMouseDownFrame = (S32) LLFrameTimer::getFrameCount();
 	
 	if (getSoundFlags() & MOUSE_DOWN)
 	{
@@ -385,7 +385,7 @@ BOOL LLButton::handleHover(S32 x, S32 y, MASK mask)
 	if (mMouseDownTimer.getStarted() && NULL != mHeldDownCallback)
 	{
 		F32 elapsed = getHeldDownTime();
-		if( mHeldDownDelay <= elapsed && mHeldDownFrameDelay <= LLFrameTimer::getFrameCount() - mMouseDownFrame)
+		if( mHeldDownDelay <= elapsed && mHeldDownFrameDelay <= (S32)LLFrameTimer::getFrameCount() - mMouseDownFrame)
 		{
 			mHeldDownCallback( mCallbackUserData );		
 		}
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 2b5691ffe87b199c0e461a26e559cb7d79532437..8140f00167e00eb3acee05121f5ec7ce0de0ac06 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -3013,6 +3013,7 @@ void LLAppViewer::idle()
 	static LLTimer idle_timer;
 
 	LLFrameTimer::updateFrameTime();
+	LLFrameTimer::updateFrameCount();
 	LLEventTimer::updateClass();
 	LLCriticalDamp::updateInterpolants();
 	LLMortician::updateClass();
diff --git a/indra/newview/llfloatercamera.cpp b/indra/newview/llfloatercamera.cpp
index 24b82c370aa295c92008e8689ce75105782869bf..75e1d694a43021490850a9e9df016c442cabaf48 100644
--- a/indra/newview/llfloatercamera.cpp
+++ b/indra/newview/llfloatercamera.cpp
@@ -52,6 +52,7 @@ LLFloaterCamera::LLFloaterCamera(const LLSD& val)
 {
 	setIsChrome(TRUE);
 	
+	// For now, only used for size and tooltip strings
 	const BOOL DONT_OPEN = FALSE;
 	LLUICtrlFactory::getInstance()->buildFloater(this, "floater_camera.xml", NULL, DONT_OPEN);
 	
diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt
index 32579e4fe2396e265ea7097ff82d2408830448c4..63e5538388bfe10ad552ed30c7bc393949b59529 100644
--- a/indra/test/CMakeLists.txt
+++ b/indra/test/CMakeLists.txt
@@ -55,6 +55,7 @@ set(test_SOURCE_FILES
     llsdutil_tut.cpp
     llservicebuilder_tut.cpp
     llstreamtools_tut.cpp
+    llstring_tut.cpp
     lltemplatemessagebuilder_tut.cpp
     lltiming_tut.cpp
     lltut.cpp