Index: binaries/data/mods/public/simulation/components/AIInterface.js
===================================================================
--- binaries/data/mods/public/simulation/components/AIInterface.js	(revision 18101)
+++ binaries/data/mods/public/simulation/components/AIInterface.js	(working copy)
@@ -48,6 +48,8 @@
 			continue;
 		if (typeof this[key] == "function")
 			continue;
+		if (key == "templates")
+			continue;
 		state[key] = this[key];
 	}
 	return state;
Index: source/simulation2/components/CCmpRangeManager.cpp
===================================================================
--- source/simulation2/components/CCmpRangeManager.cpp	(revision 18101)
+++ source/simulation2/components/CCmpRangeManager.cpp	(working copy)
@@ -415,7 +415,7 @@
 
 		serialize.Bool("global visibility update", m_GlobalVisibilityUpdate);
 		SerializeVector<SerializeU8_Unbounded>()(serialize, "global player visibility update", m_GlobalPlayerVisibilityUpdate);
-		SerializeVector<SerializeU16_Unbounded>()(serialize, "dirty visibility", m_DirtyVisibility);
+		SerializeRepetitiveVector<SerializeU16_Unbounded>()(serialize, "dirty visibility", m_DirtyVisibility);
 		SerializeVector<SerializeU32_Unbounded>()(serialize, "modified entities", m_ModifiedEntities);
 
 		// We don't serialize m_Subdivision, m_LosPlayerCounts or m_LosTiles
@@ -422,7 +422,7 @@
 		// since they can be recomputed from the entity data when deserializing;
 		// m_LosState must be serialized since it depends on the history of exploration
 
-		SerializeVector<SerializeU32_Unbounded>()(serialize, "los state", m_LosState);
+		SerializeRepetitiveVector<SerializeU32_Unbounded>()(serialize, "los state", m_LosState);
 		SerializeVector<SerializeU32_Unbounded>()(serialize, "shared los masks", m_SharedLosMasks);
 		SerializeVector<SerializeU16_Unbounded>()(serialize, "shared dirty visibility masks", m_SharedDirtyVisibilityMasks);
 	}
Index: source/simulation2/components/CCmpTemplateManager.cpp
===================================================================
--- source/simulation2/components/CCmpTemplateManager.cpp	(revision 18101)
+++ source/simulation2/components/CCmpTemplateManager.cpp	(working copy)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015 Wildfire Games.
+/* Copyright (C) 2016 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
@@ -21,6 +21,7 @@
 #include "ICmpTemplateManager.h"
 
 #include "simulation2/MessageTypes.h"
+#include "simulation2/serialization/SerializeTemplates.h"
 
 #include "ps/TemplateLoader.h"
 
@@ -58,27 +59,21 @@
 
 	virtual void Serialize(ISerializer& serialize)
 	{
-		size_t count = 0;
+		std::map<CStr, std::vector<entity_id_t>> templateMap;
 
-		for (std::map<entity_id_t, std::string>::const_iterator it = m_LatestTemplates.begin(); it != m_LatestTemplates.end(); ++it)
+		for (std::pair<entity_id_t, std::string> templateEnt : m_LatestTemplates)
 		{
-			if (ENTITY_IS_LOCAL(it->first))
+			if (ENTITY_IS_LOCAL(templateEnt.first))
 				continue;
-			++count;
+			templateMap[templateEnt.second].push_back(templateEnt.first);
 		}
-		serialize.NumberU32_Unbounded("num entities", (u32)count);
 
-		for (std::map<entity_id_t, std::string>::const_iterator it = m_LatestTemplates.begin(); it != m_LatestTemplates.end(); ++it)
+		serialize.NumberU32_Unbounded("num templates", (u32)templateMap.size());
+		for (std::pair<CStr, std::vector<entity_id_t>> list : templateMap)
 		{
-			if (ENTITY_IS_LOCAL(it->first))
-				continue;
-			serialize.NumberU32_Unbounded("id", it->first);
-			serialize.StringASCII("template", it->second, 0, 256);
+			serialize.StringASCII("template", list.first, 0, 256);
+			SerializeVector<SerializeU32_Unbounded>()(serialize, "ids", list.second);
 		}
-		// TODO: maybe we should do some kind of interning thing instead of printing so many strings?
-
-		// TODO: will need to serialize techs too, because we need to be giving out
-		// template data before other components (like the tech components) have been deserialized
 	}
 
 	virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize)
@@ -85,15 +80,16 @@
 	{
 		Init(paramNode);
 
-		u32 numEntities;
-		deserialize.NumberU32_Unbounded("num entities", numEntities);
-		for (u32 i = 0; i < numEntities; ++i)
+		u32 numTemplates;
+		deserialize.NumberU32_Unbounded("num entities", numTemplates);
+		for (u32 i = 0; i < numTemplates; ++i)
 		{
-			entity_id_t ent;
 			std::string templateName;
-			deserialize.NumberU32_Unbounded("id", ent);
+			std::vector<entity_id_t> ids;
 			deserialize.StringASCII("template", templateName, 0, 256);
-			m_LatestTemplates[ent] = templateName;
+			SerializeVector<SerializeU32_Unbounded>()(deserialize, "ids", ids);
+			for (entity_id_t id : ids)
+				m_LatestTemplates[id] = templateName;	
 		}
 	}
 
Index: source/simulation2/serialization/SerializeTemplates.h
===================================================================
--- source/simulation2/serialization/SerializeTemplates.h	(revision 18101)
+++ source/simulation2/serialization/SerializeTemplates.h	(working copy)
@@ -24,6 +24,7 @@
  */
 
 #include "simulation2/components/ICmpPathfinder.h"
+#include <type_traits>
 
 template<typename ELEM>
 struct SerializeVector
@@ -53,6 +54,53 @@
 	}
 };
 
+template<typename ELEM>
+struct SerializeRepetitiveVector
+{
+	template<typename T>
+	void operator()(ISerializer& serialize, const char* name, std::vector<T>& value)
+	{
+		size_t len = value.size();
+		serialize.NumberU32_Unbounded("length", (u32)len);
+		if (len == 0)
+			return;
+		u32 count = 1;
+		T prevVal = value[0];
+		for (size_t i = 1; i < len; ++i)
+		{
+			if (prevVal == value[i])
+			{
+				count++;
+				continue;
+			}
+			serialize.NumberU32_Unbounded("#", count);
+			ELEM()(serialize, name, prevVal);
+			count = 1;
+			prevVal = value[i];
+		}
+		serialize.NumberU32_Unbounded("#", count);
+		ELEM()(serialize, name, prevVal);
+	}
+
+	template<typename T>
+	void operator()(IDeserializer& deserialize, const char* name, std::vector<T>& value)
+	{
+		value.clear();
+		u32 len;
+		deserialize.NumberU32_Unbounded("length", len);
+		value.reserve(len); // TODO: watch out for out-of-memory
+		for (size_t i = 0; i < len;)
+		{
+			u32 count;
+			deserialize.NumberU32_Unbounded("#", count);
+			T el;
+			ELEM()(deserialize, name, el);
+			i += count;
+			value.insert(value.end(), count, el);
+		}
+	}
+};
+
 template<typename KS, typename VS>
 struct SerializeMap
 {
