Index: binaries/data/mods/public/art/actors/structures/hellenes/siege_tower.xml
===================================================================
--- binaries/data/mods/public/art/actors/structures/hellenes/siege_tower.xml	(revision 18138)
+++ binaries/data/mods/public/art/actors/structures/hellenes/siege_tower.xml	(working copy)
@@ -17,12 +17,15 @@
     </variant>
   </group>
   <group>
-    <variant frequency="100" name="Idle"/>
+    <variant frequency="1" name="ungarrisoned"/>
     <variant name="garrisoned">
       <props>
         <prop actor="props/special/common/waypoint_flag_0ad.xml" attachpoint="garrisoned"/>
       </props>
     </variant>
+  </group>
+  <group>
+    <variant frequency="1" name="alive"/>
     <variant name="death">
       <props>
         <prop attachpoint="garrisoned"/>
Index: binaries/data/mods/public/simulation/components/GarrisonHolder.js
===================================================================
--- binaries/data/mods/public/simulation/components/GarrisonHolder.js	(revision 18138)
+++ binaries/data/mods/public/simulation/components/GarrisonHolder.js	(working copy)
@@ -566,16 +566,11 @@
 
 GarrisonHolder.prototype.UpdateGarrisonFlag = function()
 {
-	var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
+	let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
 	if (!cmpVisual)
 		return;
-	cmpVisual.SelectAnimation("garrisoned", true, 0, "");
-	// TODO: ought to extend ICmpVisual to let us just select variant
-	// keywords without changing the animation too
-	if (this.entities.length)
-		cmpVisual.SelectAnimation("garrisoned", false, 1.0, "");
-	else
-		cmpVisual.SelectAnimation("idle", false, 1.0, "");
+	let selection = this.entities.length ? "garrisoned" : "ungarrisoned";
+	cmpVisual.SetVariantSelection("garrison", selection);
 };
 
 /**
Index: source/graphics/Unit.cpp
===================================================================
--- source/graphics/Unit.cpp	(revision 18138)
+++ source/graphics/Unit.cpp	(working copy)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2013 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
@@ -77,18 +77,22 @@
 		m_Animation->SetEntityID(id);
 }
 
-void CUnit::SetEntitySelection(const CStr& selection)
+void CUnit::SetEntitySelection(const CStr& key, const CStr& selection)
 {
 	CStr selection_lc = selection.LowerCase();
 
-	// If we've already selected this, don't do anything
-	if (m_EntitySelections.find(selection_lc) != m_EntitySelections.end())
+	if (m_EntitySelections[key] == selection_lc)
 		return;
+	m_EntitySelections[key] = selection_lc;
 
-	// Just allow one selection at a time
-	m_EntitySelections.clear();
-	m_EntitySelections.insert(selection_lc);
+	ReloadObject();
+}
 
+void CUnit::SetEntitySelection(const std::map<CStr, CStr>& selections)
+{
+	for (const std::pair<CStr, CStr>& s : selections)
+		m_EntitySelections[s.first] = s.second.LowerCase();
+
 	ReloadObject();
 }
 
@@ -100,9 +104,12 @@
 
 void CUnit::ReloadObject()
 {
+	std::set<CStr> entitySelections;
+	for (const std::pair<CStr, CStr>& selection : m_EntitySelections)
+		entitySelections.insert(selection.second);
+	// TODO: push world selections (seasons, etc) (and reload whenever they're changed)
 	std::vector<std::set<CStr> > selections;
-	// TODO: push world selections (seasons, etc) (and reload whenever they're changed)
-	selections.push_back(m_EntitySelections);
+	selections.push_back(entitySelections);
 	selections.push_back(m_ActorSelections);
 
 	// randomly select any remain selections necessary to completely identify a variation (e.g., the new selection
Index: source/graphics/Unit.h
===================================================================
--- source/graphics/Unit.h	(revision 18138)
+++ source/graphics/Unit.h	(working copy)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2013 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
@@ -64,8 +64,9 @@
 	void UpdateModel(float frameTime);
 
 	// Sets the entity-selection, and updates the unit to use the new
-	// actor variation.
-	void SetEntitySelection(const CStr& selection);
+	// actor variation. Either set one key at a time, or a complete map.
+	void SetEntitySelection(const CStr& key, const CStr& selection);
+	void SetEntitySelection(const std::map<CStr, CStr>& selections);
 
 	// Most units have a hopefully-unique ID number, so they can be referred to
 	// persistently despite saving/loading maps. Default for new units is -1; should
@@ -95,7 +96,7 @@
 	// actor-level selections for this unit
 	std::set<CStr> m_ActorSelections;
 	// entity-level selections for this unit
-	std::set<CStr> m_EntitySelections;
+	std::map<CStr, CStr> m_EntitySelections;
 
 	// object manager which looks after this unit's objectentry
 	CObjectManager& m_ObjectManager;
Index: source/simulation2/components/CCmpRallyPointRenderer.cpp
===================================================================
--- source/simulation2/components/CCmpRallyPointRenderer.cpp	(revision 18138)
+++ source/simulation2/components/CCmpRallyPointRenderer.cpp	(working copy)
@@ -556,7 +556,7 @@
 
 		CmpPtr<ICmpVisual> cmpVisualActor(GetSimContext(), m_MarkerEntityIds[i]);
 		if (cmpVisualActor)
-			cmpVisualActor->SetUnitEntitySelection(CStrW(cmpPlayer->GetCiv()).ToUTF8());
+			cmpVisualActor->SetVariantSelection("civ", CStrW(cmpPlayer->GetCiv()).ToUTF8());
 	}
 	m_LastMarkerCount = m_RallyPoints.size() - 1;
 }
Index: source/simulation2/components/CCmpVisualActor.cpp
===================================================================
--- source/simulation2/components/CCmpVisualActor.cpp	(revision 18138)
+++ source/simulation2/components/CCmpVisualActor.cpp	(working copy)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2014 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
@@ -32,6 +32,8 @@
 #include "ICmpValueModificationManager.h"
 #include "ICmpVisibility.h"
 
+#include "simulation2/serialization/SerializeTemplates.h"
+
 #include "graphics/Decal.h"
 #include "graphics/Frustum.h"
 #include "graphics/Model.h"
@@ -80,6 +82,8 @@
 	fixed m_AnimDesync;
 	fixed m_AnimSyncRepeatTime; // 0.0 if not synced
 
+	std::map<CStr, CStr> m_VariantSelections;
+
 	u32 m_Seed; // seed used for random variations
 
 	bool m_ConstructionPreview;
@@ -229,8 +233,9 @@
 		serialize.NumberFixed_Unbounded("anim desync", m_AnimDesync);
 		serialize.NumberFixed_Unbounded("anim sync repeat time", m_AnimSyncRepeatTime);
 
+		SerializeMap<SerializeString, SerializeString>()(serialize, "variation", m_VariantSelections);
+
 		serialize.NumberU32_Unbounded("seed", m_Seed);
-		// TODO: variation/selection strings
 		serialize.String("actor", m_ActorName, 0, 256);
 
 		// TODO: store actor variables?
@@ -259,6 +264,8 @@
 		// If we serialized a different seed or different actor, reload actor
 		if (oldSeed != GetActorSeed() || m_BaseActorName != m_ActorName)
 			ReloadActor();
+		else
+			m_Unit->SetEntitySelection(m_VariantSelections);
 
 		fixed repeattime = m_AnimSyncRepeatTime; // save because SelectAnimation overwrites it
 
@@ -410,6 +417,14 @@
 		return CVector3D();
 	}
 
+	virtual void SetVariantSelection(const CStr& key, const CStr& selection)
+	{
+		m_VariantSelections[key] = selection;
+		if (!m_Unit)
+			return;
+		m_Unit->SetEntitySelection(key, selection);
+	}
+
 	virtual void SelectAnimation(const std::string& name, bool once, fixed speed, const std::wstring& soundgroup)
 	{
 		m_AnimRunThreshold = fixed::Zero();
@@ -422,7 +437,7 @@
 
 		if (m_Unit)
 		{
-			m_Unit->SetEntitySelection(m_AnimName);
+			SetVariantSelection("animation", m_AnimName);
 			if (m_Unit->GetAnimation())
 				m_Unit->GetAnimation()->SetAnimationState(m_AnimName, m_AnimOnce, m_AnimSpeed.ToFloat(), m_AnimDesync.ToFloat(), m_SoundGroup.c_str());
 		}
@@ -440,14 +455,6 @@
 			m_AnimOverride.erase(name);
 	}
 
-	virtual void SetUnitEntitySelection(const CStr& selection)
-	{
-		if (m_Unit)
-		{
-			m_Unit->SetEntitySelection(selection);
-		}
-	}
-
 	virtual void SelectMovementAnimation(fixed runThreshold)
 	{
 		m_AnimRunThreshold = runThreshold;
@@ -454,7 +461,7 @@
 
 		if (m_Unit)
 		{
-			m_Unit->SetEntitySelection("walk");
+			SetVariantSelection("animation", "walk");
 			if (m_Unit->GetAnimation())
 				m_Unit->GetAnimation()->SetAnimationState("walk", false, 1.f, 0.f, L"");
 		}
@@ -713,7 +720,8 @@
 
 	InitModel(node->GetChild("VisualActor"));
 
-	m_Unit->SetEntitySelection(m_AnimName);
+	m_Unit->SetEntitySelection(m_VariantSelections);
+
 	if (m_Unit->GetAnimation())
 		m_Unit->GetAnimation()->SetAnimationState(m_AnimName, m_AnimOnce, m_AnimSpeed.ToFloat(), m_AnimDesync.ToFloat(), m_SoundGroup.c_str());
 
@@ -763,17 +771,12 @@
 		if (it != m_AnimOverride.end())
 			name = it->second;
 
-		m_Unit->SetEntitySelection(name);
-		if (speed == 0.0f)
+		SetVariantSelection("animation", name);
+		if (m_Unit->GetAnimation())
 		{
-			m_Unit->SetEntitySelection(name);
-			if (m_Unit->GetAnimation())
+			if (speed == 0.0f)
 				m_Unit->GetAnimation()->SetAnimationState(name, false, 1.f, 0.f, L"");
-		}
-		else
-		{
-			m_Unit->SetEntitySelection(name);
-			if (m_Unit->GetAnimation())
+			else
 				m_Unit->GetAnimation()->SetAnimationState(name, false, speed, 0.f, L"");
 		}
 	}
Index: source/simulation2/components/ICmpVisual.cpp
===================================================================
--- source/simulation2/components/ICmpVisual.cpp	(revision 18138)
+++ source/simulation2/components/ICmpVisual.cpp	(working copy)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2013 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
@@ -22,6 +22,7 @@
 #include "simulation2/system/InterfaceScripted.h"
 
 BEGIN_INTERFACE_WRAPPER(Visual)
+DEFINE_INTERFACE_METHOD_2("SetVariantSelection", void, ICmpVisual, SetVariantSelection, CStr, CStr)
 DEFINE_INTERFACE_METHOD_4("SelectAnimation", void, ICmpVisual, SelectAnimation, std::string, bool, fixed, std::wstring)
 DEFINE_INTERFACE_METHOD_1("SelectMovementAnimation", void, ICmpVisual, SelectMovementAnimation, fixed)
 DEFINE_INTERFACE_METHOD_1("ResetMoveAnimation", void, ICmpVisual, ResetMoveAnimation, std::string)
Index: source/simulation2/components/ICmpVisual.h
===================================================================
--- source/simulation2/components/ICmpVisual.h	(revision 18138)
+++ source/simulation2/components/ICmpVisual.h	(working copy)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2013 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
@@ -80,6 +80,11 @@
 	virtual CUnit* GetUnit() = 0;
 
 	/**
+	 * Set the variant selection of the actor for a certain key
+	 */
+	virtual void SetVariantSelection(const CStr& key, const CStr& selection) = 0;
+
+	/**
 	 * Start playing the given animation. If there are multiple possible animations then it will
 	 * pick one at random (not network-synchronised).
 	 * If @p soundgroup is specified, then the sound will be played at each 'event' point in the
@@ -107,11 +112,6 @@
 	virtual void ResetMoveAnimation(const std::string& name) = 0;
 
 	/**
-	 * Sets the specified entity selection on the underlying unit.
-	 */
-	virtual void SetUnitEntitySelection(const CStr& selection) = 0;
-
-	/**
 	 * Start playing the walk/run animations, scaled to the unit's movement speed.
 	 * @param runThreshold movement speed at which to switch to the run animation
 	 */
