diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js
index 23ed23a..8034091 100644
--- a/binaries/data/mods/public/gui/session/input.js
+++ b/binaries/data/mods/public/gui/session/input.js
@@ -17,21 +17,21 @@ const ACTION_GARRISON = 1;
 const ACTION_REPAIR = 2;
 var preSelectedAction = ACTION_NONE;
 
-var INPUT_NORMAL = 0;
-var INPUT_SELECTING = 1;
-var INPUT_BANDBOXING = 2;
-var INPUT_BUILDING_PLACEMENT = 3;
-var INPUT_BUILDING_CLICK = 4;
-var INPUT_BUILDING_DRAG = 5;
-var INPUT_BATCHTRAINING = 6;
-var INPUT_PRESELECTEDACTION = 7;
+const INPUT_NORMAL = 0;
+const INPUT_SELECTING = 1;
+const INPUT_BANDBOXING = 2;
+const INPUT_BUILDING_PLACEMENT = 3;
+const INPUT_BUILDING_CLICK = 4;
+const INPUT_BUILDING_DRAG = 5;
+const INPUT_BATCHTRAINING = 6;
+const INPUT_PRESELECTEDACTION = 7;
+const INPUT_BUILDING_WALL_CLICK = 8;
+const INPUT_BUILDING_WALL_PATHING = 9;
 
 var inputState = INPUT_NORMAL;
 
-var defaultPlacementAngle = Math.PI*3/4;
-var placementAngle = undefined;
-var placementPosition = undefined;
-var placementEntity = undefined;
+const defaultPlacementAngle = Math.PI*3/4;
+var placementSupport = new PlacementSupport();
 
 var mouseX = 0;
 var mouseY = 0;
@@ -88,29 +88,46 @@ function updateCursorAndTooltip()
 function updateBuildingPlacementPreview()
 {
 	// The preview should be recomputed every turn, so that it responds
-	// to obstructions/fog/etc moving underneath it
+	// to obstructions/fog/etc moving underneath it, or in the case of
+	// the wall previews, in response to new tower foundations getting
+	// constructed for it to snap to.
 
-	if (placementEntity && placementPosition)
+	if (placementSupport.mode === "building")
 	{
-		return Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
-			"template": placementEntity,
-			"x": placementPosition.x,
-			"z": placementPosition.z,
-			"angle": placementAngle
-		});
+		if (placementSupport.template && placementSupport.position)
+		{
+			return Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
+				"template": placementSupport.template,
+				"x": placementSupport.position.x,
+				"z": placementSupport.position.z,
+				"angle": placementSupport.angle,
+			});
+		}
+	}
+	else if (placementSupport.mode === "wall")
+	{
+		if (placementSupport.wallSet && placementSupport.position)
+		{
+			// Fetch an updated list of snapping candidate entities
+			placementSupport.wallSnapEntities = Engine.PickSimilarFriendlyEntities(
+				placementSupport.wallSet.tower,
+				placementSupport.wallSnapEntitiesIncludeOffscreen,
+				true, // require exact template match
+				true  // include foundations
+			);
+			
+			return Engine.GuiInterfaceCall("SetWallPlacementPreview", {
+				"wallSet": placementSupport.wallSet,
+				"start": placementSupport.position,
+				"end": placementSupport.wallEndPosition,
+				"snapEntities": placementSupport.wallSnapEntities,	// snapping entities (towers) for starting a wall segment
+			});
+		}
 	}
 
 	return false;
 }
 
-function resetPlacementEntity()
-{
-	Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {"template": ""});
-	placementEntity = undefined;
-	placementPosition = undefined;
-	placementAngle = undefined;
-}
-
 function findGatherType(gatherer, supply)
 {
 	if (!gatherer || !supply)
@@ -434,6 +451,12 @@ var dragStart; // used for remembering mouse coordinates at start of drag operat
 
 function tryPlaceBuilding(queued)
 {
+	if (placementSupport.mode !== "building")
+	{
+		error("[tryPlaceBuilding] Called while in '"+placementSupport.mode+"' placement mode instead of 'building'");
+		return false;
+	}
+	
 	var selection = g_Selection.toList();
 
 	// Use the preview to check it's a valid build location
@@ -447,10 +470,10 @@ function tryPlaceBuilding(queued)
 	// Start the construction
 	Engine.PostNetworkCommand({
 		"type": "construct",
-		"template": placementEntity,
-		"x": placementPosition.x,
-		"z": placementPosition.z,
-		"angle": placementAngle,
+		"template": placementSupport.template,
+		"x": placementSupport.position.x,
+		"z": placementSupport.position.z,
+		"angle": placementSupport.angle,
 		"entities": selection,
 		"autorepair": true,
 		"autocontinue": true,
@@ -459,7 +482,59 @@ function tryPlaceBuilding(queued)
 	Engine.GuiInterfaceCall("PlaySound", { "name": "order_repair", "entity": selection[0] });
 
 	if (!queued)
-		resetPlacementEntity();
+		placementSupport.Reset();
+
+	return true;
+}
+
+function tryPlaceWall()
+{
+	if (placementSupport.mode !== "wall")
+	{
+		error("[tryPlaceWall] Called while in '" + placementSupport.mode + "' placement mode; expected 'wall' mode");
+		return false;
+	}
+	
+	var wallPlacementInfo = updateBuildingPlacementPreview(); // entities making up the wall (wall segments, towers, ...)
+	if (!(wallPlacementInfo === false || typeof(wallPlacementInfo) === "object"))
+	{
+		error("[tryPlaceWall] Unexpected return value from updateBuildingPlacementPreview: '" + uneval(placementInfo) + "'; expected either 'false' or 'object'");
+		return false;
+	}
+	
+	if (!wallPlacementInfo)
+		return false;
+	
+	var selection = g_Selection.toList();
+	var cmd = {
+		"type": "construct-wall",
+		"autorepair": true,
+		"autocontinue": true,
+		"queued": true,
+		"entities": selection,
+		"wallSet": placementSupport.wallSet,
+		"pieces": wallPlacementInfo.pieces,
+		"startSnappedEntity": wallPlacementInfo.startSnappedEnt,
+		"endSnappedEntity": wallPlacementInfo.endSnappedEnt,
+	};
+	
+	//warn("-------------");
+	//for (var k in cmd)
+	//	warn("  " + k + ": " + uneval(cmd[k]));
+	//warn("-------------");
+	
+	/*for each (var entInfo in wallPlacementInfo.pieces)
+	{
+		cmd.pieces.push({
+			"template": entInfo.template,
+			"x": entInfo.x,
+			"z": entInfo.z,
+			"angle": entInfo.angle,
+		});
+	}*/
+	
+	Engine.PostNetworkCommand(cmd);
+	Engine.GuiInterfaceCall("PlaySound", {"name": "order_repair", "entity": selection[0] });
 
 	return true;
 }
@@ -659,7 +734,7 @@ function handleInputBeforeGui(ev, hoveredObject)
 			if (ev.button == SDL_BUTTON_RIGHT)
 			{
 				// Cancel building
-				resetPlacementEntity();
+				placementSupport.Reset();
 				inputState = INPUT_NORMAL;
 				return true;
 			}
@@ -667,6 +742,98 @@ function handleInputBeforeGui(ev, hoveredObject)
 		}
 		break;
 
+	case INPUT_BUILDING_WALL_CLICK:
+		// User is mid-click in choosing a starting point for building a wall. The build process can still be cancelled at this point
+		// by right-clicking; releasing the left mouse button will 'register' the starting point and commence endpoint choosing mode. 
+		switch (ev.type)
+		{
+		case "mousebuttonup":
+			if (ev.button === SDL_BUTTON_LEFT)
+			{
+    			inputState = INPUT_BUILDING_WALL_PATHING; warn("-> INPUT_BUILDING_WALL_PATHING");
+    			return true;
+			}
+			break;
+			
+		case "mousebuttondown":
+			if (ev.button == SDL_BUTTON_RIGHT)
+			{
+				// Cancel building
+				placementSupport.Reset();
+				updateBuildingPlacementPreview();
+				
+				inputState = INPUT_NORMAL; warn("-> INPUT_NORMAL");
+				return true;
+			}
+			break;
+		}
+		break;
+	
+	case INPUT_BUILDING_WALL_PATHING:
+		// User has chosen a starting point for constructing the wall, and is now looking to set the endpoint.
+		// Right-clicking cancels wall building mode, left-clicking sets the endpoint and builds the wall and returns to
+		// normal input mode. Optionally, shift + left-clicking does not return to normal input, and instead allows the 
+		// user to continue building walls.
+		switch (ev.type)
+		{
+			case "mousemotion":
+				placementSupport.wallEndPosition = Engine.GetTerrainAtPoint(ev.x, ev.y);
+				
+				// Update the building placement preview, and by extension, the list of snapping candidate entities for both (!)
+				// the ending point and the starting point to snap to.
+				// 
+				// TODO: Note that here, we need to fetch all similar entities, including any offscreen ones, to support the case
+				// where the snap entity for the starting point has moved offscreen, or has been deleted/destroyed, or was a
+				// foundation and has been replaced with a completed entity since the user first chose it. Fetching all towers on
+				// the entire map instead of only the current screen might get expensive fast since walls all have a ton of towers
+				// in them. Might be useful to query only for entities within a certain range around the starting point and ending
+				// points.
+				
+				placementSupport.wallSnapEntitiesIncludeOffscreen = true;
+				updateBuildingPlacementPreview(); // includes an update of the snap entity candidates
+				break;
+				
+			case "mousebuttondown":
+				if (ev.button == SDL_BUTTON_LEFT)
+				{
+					if (tryPlaceWall())
+					{
+    					if (Engine.HotkeyIsPressed("session.queue"))
+    					{
+    						// continue building, just set a new starting position where we left off
+    						placementSupport.position = placementSupport.wallEndPosition;
+    						placementSupport.wallEndPosition = undefined;
+    						
+    						inputState = INPUT_BUILDING_WALL_CLICK; warn("-> INPUT_BUILDING_WALL_CLICK");
+    					}
+    					else
+    					{
+    						placementSupport.Reset();
+    						inputState = INPUT_NORMAL; warn("-> INPUT_NORMAL");
+    					}
+					}
+					else
+					{
+						// TODO: display some useful error message to the player
+						error("Cannot build wall here!");
+					}
+					
+					updateBuildingPlacementPreview();
+					return true;
+				}
+				else if (ev.button == SDL_BUTTON_RIGHT)
+				{
+					// reset to normal input mode
+					placementSupport.Reset();
+					updateBuildingPlacementPreview();
+					
+					inputState = INPUT_NORMAL; warn("-> INPUT_NORMAL");
+					return true;
+				}
+				break;
+		}
+		break;
+		
 	case INPUT_BUILDING_DRAG:
 		switch (ev.type)
 		{
@@ -678,24 +845,24 @@ function handleInputBeforeGui(ev, hoveredObject)
 			{
 				// Rotate in the direction of the mouse
 				var target = Engine.GetTerrainAtPoint(ev.x, ev.y);
-				placementAngle = Math.atan2(target.x - placementPosition.x, target.z - placementPosition.z);
+				placementSupport.angle = Math.atan2(target.x - placementSupport.position.x, target.z - placementSupport.position.z);
 			}
 			else
 			{
 				// If the mouse is near the center, snap back to the default orientation
-				placementAngle = defaultPlacementAngle;
+				placementSupport.SetDefaultAngle();
 			}
 
 			var snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", {
-				"template": placementEntity,
-				"x": placementPosition.x,
-				"z": placementPosition.z
+				"template": placementSupport.template,
+				"x": placementSupport.position.x,
+				"z": placementSupport.position.z
 			});
 			if (snapData)
 			{
-				placementAngle = snapData.angle;
-				placementPosition.x = snapData.x;
-				placementPosition.z = snapData.z;
+				placementSupport.angle = snapData.angle;
+				placementSupport.position.x = snapData.x;
+				placementSupport.position.z = snapData.z;
 			}
 
 			updateBuildingPlacementPreview();
@@ -725,7 +892,7 @@ function handleInputBeforeGui(ev, hoveredObject)
 			if (ev.button == SDL_BUTTON_RIGHT)
 			{
 				// Cancel building
-				resetPlacementEntity();
+				placementSupport.Reset();
 				inputState = INPUT_NORMAL;
 				return true;
 			}
@@ -819,6 +986,7 @@ function handleInputAfterGui(ev)
 				break;
 		}
 		break;
+		
 	case INPUT_PRESELECTEDACTION:
 		switch (ev.type)
 		{
@@ -849,6 +1017,7 @@ function handleInputAfterGui(ev)
 			}
 		}
 		break;
+		
 	case INPUT_SELECTING:
 		switch (ev.type)
 		{
@@ -922,7 +1091,7 @@ function handleInputAfterGui(ev)
 					}
 
 					// TODO: Should we handle "control all units" here as well?
-					ents = Engine.PickSimilarFriendlyEntities(templateToMatch, showOffscreen, matchRank);
+					ents = Engine.PickSimilarFriendlyEntities(templateToMatch, showOffscreen, matchRank, false);
 				}
 				else
 				{
@@ -961,35 +1130,62 @@ function handleInputAfterGui(ev)
 		switch (ev.type)
 		{
 		case "mousemotion":
-			placementPosition = Engine.GetTerrainAtPoint(ev.x, ev.y);
-			var snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", {
-				"template": placementEntity,
-				"x": placementPosition.x,
-				"z": placementPosition.z
-			});
-			if (snapData)
+			
+			placementSupport.position = Engine.GetTerrainAtPoint(ev.x, ev.y);
+			
+			if (placementSupport.mode === "wall")
 			{
-				placementAngle = snapData.angle;
-				placementPosition.x = snapData.x;
-				placementPosition.z = snapData.z;
+				// Including only the on-screen towers in the next snap candidate list is sufficient here, since the user is
+				// still selecting a starting point (which must necessarily be on-screen). (The update itself happens in the
+				// call to updateBuildingPlacementPreview below).
+				placementSupport.wallSnapEntitiesIncludeOffscreen = false;
+			}
+			else
+			{
+				var snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", {
+					"template": placementSupport.template,
+					"x": placementSupport.position.x,
+					"z": placementSupport.position.z,
+				});
+				if (snapData)
+				{
+					placementSupport.angle = snapData.angle;
+					placementSupport.position.x = snapData.x;
+					placementSupport.position.z = snapData.z;
+				}
 			}
 
-			updateBuildingPlacementPreview();
-
+			updateBuildingPlacementPreview(); // includes an update of the snap entity candidates
 			return false; // continue processing mouse motion
 
 		case "mousebuttondown":
 			if (ev.button == SDL_BUTTON_LEFT)
 			{
-				placementPosition = Engine.GetTerrainAtPoint(ev.x, ev.y);
-				dragStart = [ ev.x, ev.y ];
-				inputState = INPUT_BUILDING_CLICK;
+				if (placementSupport.mode === "wall")
+				{
+					var validPlacement = updateBuildingPlacementPreview();
+					if (validPlacement !== false)
+					{
+						inputState = INPUT_BUILDING_WALL_CLICK;
+					}
+					else
+					{
+						// TODO: add a user-friendly notification
+						error("Not a valid starting location!");
+					}
+				}
+				else
+				{
+					placementSupport.position = Engine.GetTerrainAtPoint(ev.x, ev.y);
+    				dragStart = [ ev.x, ev.y ];
+    				inputState = INPUT_BUILDING_CLICK;
+				}
 				return true;
 			}
 			else if (ev.button == SDL_BUTTON_RIGHT)
 			{
 				// Cancel building
-				resetPlacementEntity();
+				placementSupport.Reset();
 				inputState = INPUT_NORMAL;
 				return true;
 			}
@@ -1002,11 +1198,11 @@ function handleInputAfterGui(ev)
 			switch (ev.hotkey)
 			{
 			case "session.rotate.cw":
-				placementAngle += rotation_step;
+				placementSupport.angle += rotation_step;
 				updateBuildingPlacementPreview();
 				break;
 			case "session.rotate.ccw":
-				placementAngle -= rotation_step;
+				placementSupport.angle -= rotation_step;
 				updateBuildingPlacementPreview();
 				break;
 			}
@@ -1144,11 +1340,29 @@ function handleMinimapEvent(target)
 }
 
 // Called by GUI when user clicks construction button
-function startBuildingPlacement(buildEntType)
+// @param buildTemplate Template name of the entity the user wants to build
+function startBuildingPlacement(buildTemplate)
 {
-	placementEntity = buildEntType;
-	placementAngle = defaultPlacementAngle;
-	inputState = INPUT_BUILDING_PLACEMENT;
+	// TODO: we should clear any highlight selection rings here. If the mouse was over an entity before going onto the GUI
+	// to start building a structure, then the highlight selection rings are kept during the construction of the building.
+	// Gives the impression that somehow the hovered-over entity has something to do with the building you're constructing.
+	
+	placementSupport.SetDefaultAngle();
+	
+	// find out if we're building a wall, and change the entity appropriately if so
+	var templateData = GetTemplateData(buildTemplate);
+	if (templateData.wallSet)
+	{
+		placementSupport.mode = "wall";
+		placementSupport.wallSet = templateData.wallSet;
+		inputState = INPUT_BUILDING_PLACEMENT;
+	}
+	else
+	{
+		placementSupport.mode = "building";
+		placementSupport.template = buildTemplate;
+		inputState = INPUT_BUILDING_PLACEMENT;
+	}
 }
 
 // Called by GUI when user changes preferred trading goods
diff --git a/binaries/data/mods/public/gui/session/placement.js b/binaries/data/mods/public/gui/session/placement.js
new file mode 100644
index 0000000..29d5d04
--- /dev/null
+++ b/binaries/data/mods/public/gui/session/placement.js
@@ -0,0 +1,31 @@
+function PlacementSupport()
+{
+	this.Reset();
+}
+
+PlacementSupport.DEFAULT_ANGLE = Math.PI*3/4;
+
+/**
+ * Resets the building placement support state. Use this to cancel construction of an entity.
+ */
+PlacementSupport.prototype.Reset = function()
+{
+	this.mode = null;
+	this.position = null;
+	this.template = null;
+	this.wallSet = null;             // maps types of wall pieces ("tower", "long", "short", ...) to template names
+	this.wallSnapEntities = null;    // list of candidate entities to snap the starting and (!) ending positions to when building walls
+	this.wallEndPosition = null;
+	this.wallSnapEntitiesIncludeOffscreen = false; // should the next update of the snap candidate list include offscreen towers?
+	
+	this.SetDefaultAngle();
+	
+	warn("PlacementSupport::Reset");
+	Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {"template": ""});
+	Engine.GuiInterfaceCall("SetWallPlacementPreview", {"wallSet": null});
+};
+
+PlacementSupport.prototype.SetDefaultAngle = function()
+{
+	this.angle = PlacementSupport.DEFAULT_ANGLE;
+};
\ No newline at end of file
diff --git a/binaries/data/mods/public/gui/session/session.js b/binaries/data/mods/public/gui/session/session.js
index 8ecf776..c981c18 100644
--- a/binaries/data/mods/public/gui/session/session.js
+++ b/binaries/data/mods/public/gui/session/session.js
@@ -169,6 +169,10 @@ function getSavedGameData()
 }
 
 var lastTickTime = new Date;
+
+/**
+ * Called every frame.
+ */
 function onTick()
 {
 	var now = new Date;
@@ -187,7 +191,8 @@ function onTick()
 
 	updateCursorAndTooltip();
 
-	// If the selection changed, we need to regenerate the sim display
+	// If the selection changed, we need to regenerate the sim display (the display depends on both the
+	// simulation state and the current selection).
 	if (g_Selection.dirty)
 	{
 		onSimulationUpdate();
@@ -252,6 +257,10 @@ function checkPlayerState()
 	}
 }
 
+/**
+ * Recomputes GUI state that depends on simulation state or selection state. Called directly every simulation
+ * update (see session.xml), or from onTick when the selection has changed.
+ */
 function onSimulationUpdate()
 {
 	g_Selection.dirty = false;
diff --git a/binaries/data/mods/public/gui/session/session.xml b/binaries/data/mods/public/gui/session/session.xml
index 5de7e23..f5a37f3 100644
--- a/binaries/data/mods/public/gui/session/session.xml
+++ b/binaries/data/mods/public/gui/session/session.xml
@@ -9,6 +9,7 @@
 	<script file="gui/common/timer.js"/>
 	<script file="gui/session/session.js"/>
 	<script file="gui/session/selection.js"/>
+	<script file="gui/session/placement.js"/>
 	<script file="gui/session/input.js"/>
 	<script file="gui/session/menu.js"/>
 	<script file="gui/session/selection_details.js"/>
diff --git a/binaries/data/mods/public/gui/session/unit_commands.js b/binaries/data/mods/public/gui/session/unit_commands.js
index 8e8e2da..0d84dde 100644
--- a/binaries/data/mods/public/gui/session/unit_commands.js
+++ b/binaries/data/mods/public/gui/session/unit_commands.js
@@ -120,7 +120,18 @@ function layoutButtonRow(rowNumber, guiName, buttonSideLength, buttonSpacer, sta
 	}
 }
 
-// Sets up "unit panels" - the panels with rows of icons (Helper function for updateUnitDisplay)
+/**
+ * Helper function for updateUnitCommands; sets up "unit panels" (i.e. panels with rows of icons) for the currently selected
+ * unit.
+ * 
+ * @param guiName Short identifier string of this panel; see constants defined at the top of this file.
+ * @param usedPanels Output object; usedPanels[guiName] will be set to 1 to indicate that this panel was used during this
+ *                     run of updateUnitCommands and should not be hidden. TODO: why is this done this way instead of having
+ *                     updateUnitCommands keep track of this?
+ * @param unitEntState Entity state of the (first) selected unit.
+ * @param items Panel-specific data to construct the icons with.
+ * @param callback Callback function to argument to execute when an item's icon gets clicked. Takes a single 'item' argument.
+ */
 function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
 {
 	usedPanels[guiName] = 1;
@@ -183,7 +194,7 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
 	for (i = 0; i < numberOfItems; i++)
 	{
 		var item = items[i];
-		var entType = ((guiName == "Queue")? item.template : item);
+		var entType = ((guiName == "Queue") ? item.template : item);
 		var template;
 		if (guiName != "Formation" && guiName != "Command" && guiName != "Stance")
 		{
@@ -236,7 +247,7 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
 				var [batchSize, batchIncrement] = getTrainingQueueBatchStatus(unitEntState.id, entType);
 				var trainNum = batchSize ? batchSize+batchIncrement : batchIncrement;
 
-				tooltip += "\n" + getEntityCost(template);
+				tooltip += "\n" + getEntityCostTooltip(template);
 
 				if (template.health)
 					tooltip += "\n[font=\"serif-bold-13\"]Health:[/font] " + template.health;
@@ -256,9 +267,9 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
 				if (template.tooltip)
 					tooltip += "\n[font=\"serif-13\"]" + template.tooltip + "[/font]";
 
-				tooltip += "\n" + getEntityCost(template);
+				tooltip += "\n" + getEntityCostTooltip(template); // see utility_functions.js
+				tooltip += getPopulationBonusTooltip(template); // see utility_functions.js
 
-				tooltip += getPopulationBonus(template);
 				if (template.health)
 					tooltip += "\n[font=\"serif-bold-13\"]Health:[/font] " + template.health;
 
@@ -483,7 +494,15 @@ function setupUnitBarterPanel(unitEntState)
 	}
 }
 
-// Updates right Unit Commands Panel - runs in the main session loop via updateSelectionDetails()
+/**
+ * Updates the right hand side "Unit Commands" panel. Runs in the main session loop via updateSelectionDetails().
+ * Delegates to setupUnitPanel to set up individual subpanels, appropriately activated depending on the selected
+ * unit's state.
+ * 
+ * @param entState Entity state of the (first) selected unit.
+ * @param supplementalDetailsPanel Reference to the "supplementalSelectionDetails" GUI Object
+ * @param selection Array of currently selected entity IDs.
+ */
 function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection)
 {
 	// Panels that are active
diff --git a/binaries/data/mods/public/gui/session/utility_functions.js b/binaries/data/mods/public/gui/session/utility_functions.js
index 0d95a70..bbc81f7 100644
--- a/binaries/data/mods/public/gui/session/utility_functions.js
+++ b/binaries/data/mods/public/gui/session/utility_functions.js
@@ -193,27 +193,65 @@ function getEntityCommandsList(entState)
 	return commands;
 }
 
-function getEntityCost(template)
+/**
+ * Helper function for getEntityCostTooltip.
+ */
+function getEntityCostComponentsTooltipString(template)
 {
-	var cost = "";
-	if (template.cost)
+	var costs = [];
+	if (template.cost.food) costs.push(template.cost.food + " [font=\"serif-12\"]Food[/font]");
+	if (template.cost.wood) costs.push(template.cost.wood + " [font=\"serif-12\"]Wood[/font]");
+	if (template.cost.metal) costs.push(template.cost.metal + " [font=\"serif-12\"]Metal[/font]");
+	if (template.cost.stone) costs.push(template.cost.stone + " [font=\"serif-12\"]Stone[/font]");
+	if (template.cost.population) costs.push(template.cost.population + " [font=\"serif-12\"]Population[/font]");
+	return costs;
+}
+
+/**
+ * Returns the cost information to display in the specified entity's construction button tooltip.
+ */
+function getEntityCostTooltip(template)
+{
+	var cost = "[font=\"serif-bold-13\"]Costs:[/font] ";
+	
+	// Entities with a wallset component are proxies for initiating wall placement and as such do not have a cost of
+	// their own; the individual wall pieces within it do.
+	if (template.wallSet)
+	{
+		var templateLong = GetTemplateData(template.wallSet.long);
+		var templateMedium = GetTemplateData(template.wallSet.medium);
+		var templateShort = GetTemplateData(template.wallSet.short);
+		var templateTower = GetTemplateData(template.wallSet.tower);
+		
+		// TODO: the costs of the wall segments should be the same, and for now we will assume they are (ideally we
+		// should take the average here or something).
+		var wallCosts = getEntityCostComponentsTooltipString(templateLong);
+		var towerCosts = getEntityCostComponentsTooltipString(templateTower);
+		
+		cost += "\n";
+		cost += " Walls:  " + wallCosts.join(", ") + "\n";
+		cost += " Towers: " + towerCosts.join(", ");
+	}
+	else if (template.cost)
 	{
-		var costs = [];
-		if (template.cost.food) costs.push(template.cost.food + " [font=\"serif-12\"]Food[/font]");
-		if (template.cost.wood) costs.push(template.cost.wood + " [font=\"serif-12\"]Wood[/font]");
-		if (template.cost.metal) costs.push(template.cost.metal + " [font=\"serif-12\"]Metal[/font]");
-		if (template.cost.stone) costs.push(template.cost.stone + " [font=\"serif-12\"]Stone[/font]");
-		if (template.cost.population) costs.push(template.cost.population + " [font=\"serif-12\"]Population[/font]");
-
-		cost += "[font=\"serif-bold-13\"]Costs:[/font] " + costs.join(", ");
+		var costs = getEntityCostComponentsTooltipString(template);
+		cost += costs.join(", ");
 	}
+	else
+	{
+		cost = ""; // cleaner than duplicating the serif-bold-13 stuff
+	}
+	
 	return cost;
 }
 
-function getPopulationBonus(template)
+/**
+ * Returns the population bonus information to display in the specified entity's construction button tooltip.
+ */
+function getPopulationBonusTooltip(template)
 {
 	var popBonus = "";
-	if (template.cost.populationBonus)
+	if (template.cost && template.cost.populationBonus)
 		popBonus = "\n[font=\"serif-bold-13\"]Population Bonus:[/font] " + template.cost.populationBonus;
 	return popBonus;
 }
diff --git a/binaries/data/mods/public/maps/scenarios/WallTest.pmp b/binaries/data/mods/public/maps/scenarios/WallTest.pmp
new file mode 100644
index 0000000..bf23296
Binary files /dev/null and b/binaries/data/mods/public/maps/scenarios/WallTest.pmp differ
diff --git a/binaries/data/mods/public/maps/scenarios/WallTest.xml b/binaries/data/mods/public/maps/scenarios/WallTest.xml
new file mode 100644
index 0000000..44428a8
--- /dev/null
+++ b/binaries/data/mods/public/maps/scenarios/WallTest.xml
@@ -0,0 +1,167 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<Scenario version="5">
+	<Environment>
+		<LightingModel>standard</LightingModel>
+		<SkySet>default</SkySet>
+		<SunColour r="0.74902" g="0.74902" b="0.74902"/>
+		<SunElevation angle="0.785398"/>
+		<SunRotation angle="-0.785396"/>
+		<TerrainAmbientColour r="0.501961" g="0.501961" b="0.501961"/>
+		<UnitsAmbientColour r="0.501961" g="0.501961" b="0.501961"/>
+		<Water>
+			<WaterBody>
+				<Type>default</Type>
+				<Colour r="0.294118" g="0.34902" b="0.694118"/>
+				<Height>5</Height>
+				<Shininess>150</Shininess>
+				<Waviness>8</Waviness>
+				<Murkiness>0.45</Murkiness>
+				<Tint r="0.27451" g="0.294118" b="0.584314"/>
+				<ReflectionTint r="0.27451" g="0.294118" b="0.584314"/>
+				<ReflectionTintStrength>0</ReflectionTintStrength>
+			</WaterBody>
+		</Water>
+	</Environment>
+	<Camera>
+		<Position x="292.862" y="79.7402" z="170.777"/>
+		<Rotation angle="0"/>
+		<Declination angle="0.610865"/>
+	</Camera>
+	<ScriptSettings><![CDATA[
+{
+  "CircularMap": true,
+  "Description": "Give an interesting description of your map.",
+  "GameType": "conquest",
+  "Keywords": [],
+  "LockTeams": false,
+  "Name": "WallTest",
+  "PlayerData": [
+    {
+      "AI": "",
+      "Civ": "hele",
+      "Colour": {
+        "b": 200,
+        "g": 46,
+        "r": 46
+      },
+      "Name": "Player 1",
+      "PopulationLimit": 100000,
+      "Resources": {
+        "food": 100000,
+        "metal": 100000,
+        "stone": 100000,
+        "wood": 100000
+      },
+      "StartingCamera": {
+        "Position": {
+          "x": 292.862,
+          "y": 22.3825,
+          "z": 252.692
+        },
+        "Rotation": {
+          "x": 35,
+          "y": 0,
+          "z": 0
+        }
+      },
+      "Team": -1
+    },
+    {
+      "AI": "qbot",
+      "Civ": "hele",
+      "Colour": {
+        "b": 20,
+        "g": 20,
+        "r": 150
+      },
+      "Name": "Player 2",
+      "PopulationLimit": 2,
+      "Resources": {
+        "food": 5000,
+        "metal": 5000,
+        "stone": 5000,
+        "wood": 5000
+      },
+      "Team": -1
+    }
+  ],
+  "RevealMap": false
+}
+]]></ScriptSettings>
+	<Entities>
+		<Entity uid="11">
+			<Template>structures/celt_civil_centre</Template>
+			<Player>1</Player>
+			<Position x="286.75593" z="268.53187"/>
+			<Orientation y="2.35621"/>
+		</Entity>
+		<Entity uid="12">
+			<Template>structures/pers_civil_centre</Template>
+			<Player>2</Player>
+			<Position x="751.73603" z="789.12446"/>
+			<Orientation y="2.35621"/>
+		</Entity>
+		<Entity uid="13">
+			<Template>other/palisades_rocks_tower</Template>
+			<Player>1</Player>
+			<Position x="280.52155" z="152.9048"/>
+			<Orientation y="2.35621"/>
+		</Entity>
+		<Entity uid="14">
+			<Template>other/palisades_rocks_tower</Template>
+			<Player>1</Player>
+			<Position x="405.53681" z="237.12357"/>
+			<Orientation y="2.35621"/>
+		</Entity>
+		<Entity uid="15">
+			<Template>other/palisades_rocks_tower</Template>
+			<Player>1</Player>
+			<Position x="273.05485" z="368.58619"/>
+			<Orientation y="2.35621"/>
+		</Entity>
+		<Entity uid="16">
+			<Template>other/palisades_rocks_tower</Template>
+			<Player>1</Player>
+			<Position x="162.9825" z="248.80494"/>
+			<Orientation y="2.35621"/>
+		</Entity>
+		<Entity uid="56">
+			<Template>units/celt_fanatic</Template>
+			<Player>1</Player>
+			<Position x="297.80228" z="238.4332"/>
+			<Orientation y="2.35621"/>
+		</Entity>
+		<Entity uid="57">
+			<Template>units/celt_fanatic</Template>
+			<Player>1</Player>
+			<Position x="300.73261" z="240.50628"/>
+			<Orientation y="2.35621"/>
+		</Entity>
+		<Entity uid="58">
+			<Template>units/celt_fanatic</Template>
+			<Player>1</Player>
+			<Position x="304.17972" z="243.5565"/>
+			<Orientation y="2.35621"/>
+		</Entity>
+		<Entity uid="59">
+			<Template>units/celt_fanatic</Template>
+			<Player>1</Player>
+			<Position x="308.13715" z="246.98673"/>
+			<Orientation y="2.35621"/>
+		</Entity>
+		<Entity uid="60">
+			<Template>units/celt_fanatic</Template>
+			<Player>1</Player>
+			<Position x="312.3144" z="251.31986"/>
+			<Orientation y="2.35621"/>
+		</Entity>
+		<Entity uid="61">
+			<Template>units/celt_fanatic</Template>
+			<Player>1</Player>
+			<Position x="317.41407" z="256.17164"/>
+			<Orientation y="2.35621"/>
+		</Entity>
+	</Entities>
+	<Paths/>
+</Scenario>
\ No newline at end of file
diff --git a/binaries/data/mods/public/simulation/components/BuildRestrictions.js b/binaries/data/mods/public/simulation/components/BuildRestrictions.js
index b9d1e87..b50999c 100644
--- a/binaries/data/mods/public/simulation/components/BuildRestrictions.js
+++ b/binaries/data/mods/public/simulation/components/BuildRestrictions.js
@@ -71,6 +71,9 @@ BuildRestrictions.prototype.Init = function()
 	this.territories = this.template.Territory.split(/\s+/);
 };
 
+/**
+ * Returns true iff this entity can be built at its current position.
+ */
 BuildRestrictions.prototype.CheckPlacement = function(player)
 {
 	// TODO: Return error code for invalid placement, which can be handled by the UI
diff --git a/binaries/data/mods/public/simulation/components/Foundation.js b/binaries/data/mods/public/simulation/components/Foundation.js
index 14029da..f63a1e3 100644
--- a/binaries/data/mods/public/simulation/components/Foundation.js
+++ b/binaries/data/mods/public/simulation/components/Foundation.js
@@ -187,15 +187,38 @@ Foundation.prototype.Build = function(builderEnt, work)
 		cmpBuildingPosition.SetXZRotation(rot.x, rot.z);
 		// TODO: should add a ICmpPosition::CopyFrom() instead of all this
 
+		// ----------------------------------------------------------------------
+		
 		var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
 		var cmpBuildingOwnership = Engine.QueryInterface(building, IID_Ownership);
 		cmpBuildingOwnership.SetOwner(cmpOwnership.GetOwner());
 		
+		// ----------------------------------------------------------------------
+		
+		// Copy over the obstruction control group IDs from the foundation entities. This is needed to ensure that when a foundation
+		// is completed and replaced by a new entity, it remains in the same control group(s) as any other foundation entities that 
+		// may surround it. This is the mechanism that is used to e.g. enable wall pieces to be built closely together, ignoring their
+		// mutual obstruction shapes (since they would otherwise be prevented from being built so closely together). If the control 
+		// groups are not copied over, the new entity will default to a new control group containing only itself, and will hence block
+		// construction of any surrounding foundations that it was previously in the same control group with.
+		
+		// Note that this will result in the completed building entities having control group IDs that equal entity IDs of old (and soon
+		// to be deleted) foundation entities. This should not have any consequences, however, since the control group IDs are only meant
+		// to be unique identifiers, which is still true when reusing the old ones.
+		
+		var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
+		var cmpBuildingObstruction = Engine.QueryInterface(building, IID_Obstruction);
+		cmpBuildingObstruction.SetControlGroup(cmpObstruction.GetControlGroup());
+		cmpBuildingObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2());
+		
+		// ----------------------------------------------------------------------
+		
 		var cmpPlayerStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
 		cmpPlayerStatisticsTracker.IncreaseConstructedBuildingsCounter();
 
 		var cmpIdentity = Engine.QueryInterface(building, IID_Identity);
-		if (cmpIdentity.GetClassesList().indexOf("CivCentre") != -1) cmpPlayerStatisticsTracker.IncreaseBuiltCivCentresCounter();
+		if (cmpIdentity.GetClassesList().indexOf("CivCentre") != -1)
+			cmpPlayerStatisticsTracker.IncreaseBuiltCivCentresCounter();
 		
 		var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
 		var cmpBuildingHealth = Engine.QueryInterface(building, IID_Health);
@@ -203,9 +226,7 @@ Foundation.prototype.Build = function(builderEnt, work)
 
 		PlaySound("constructed", building);
 
-		Engine.PostMessage(this.entity, MT_ConstructionFinished,
-			{ "entity": this.entity, "newentity": building });
-
+		Engine.PostMessage(this.entity, MT_ConstructionFinished, { "entity": this.entity, "newentity": building });
 		Engine.BroadcastMessage(MT_EntityRenamed, { entity: this.entity, newentity: building });
 
 		Engine.DestroyEntity(this.entity);
diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js
index 2c4a82f..47058e7 100644
--- a/binaries/data/mods/public/simulation/components/GuiInterface.js
+++ b/binaries/data/mods/public/simulation/components/GuiInterface.js
@@ -18,6 +18,8 @@ GuiInterface.prototype.Deserialize = function(obj)
 GuiInterface.prototype.Init = function()
 {
 	this.placementEntity = undefined; // = undefined or [templateName, entityID]
+	this.placementWallEntities = undefined;
+	this.placementWallLastAngle = 0;
 	this.rallyPoints = undefined;
 	this.notifications = [];
 	this.renamedEntities = [];
@@ -131,7 +133,7 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
 	var ret = {
 		"id": ent,
 		"template": template
-	}
+	};
 
 	var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
 	if (cmpIdentity)
@@ -202,6 +204,15 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
 		};
 	}
 
+	var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
+	if (cmpObstruction)
+	{
+		ret.obstruction = {
+			"controlGroup": cmpObstruction.GetControlGroup(),
+			"controlGroup2": cmpObstruction.GetControlGroup2(),
+		};
+	}
+
 	var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
 	if (cmpOwnership)
 	{
@@ -311,6 +322,26 @@ GuiInterface.prototype.GetTemplateData = function(player, name)
 		}
 	}
 	
+	if (template.BuildRestrictions)
+	{
+		// required properties
+		ret.buildRestrictions = {
+			"placementType": template.BuildRestrictions.PlacementType,
+			"territory": template.BuildRestrictions.Territory,
+			"category": template.BuildRestrictions.Category,
+		};
+		
+		// optional properties
+		if (template.BuildRestrictions.Distance)
+		{
+			ret.buildRestrictions.distance = {
+				"fromCategory": template.BuildRestrictions.Distance.FromCategory,
+			};
+			if (template.BuildRestrictions.Distance.MinDistance) ret.buildRestrictions.distance.min = +template.BuildRestrictions.Distance.MinDistance;
+			if (template.BuildRestrictions.Distance.MaxDistance) ret.buildRestrictions.distance.max = +template.BuildRestrictions.Distance.MaxDistance;
+		}
+	}
+	
 	if (template.Cost)
 	{
 		ret.cost = {};
@@ -322,6 +353,44 @@ GuiInterface.prototype.GetTemplateData = function(player, name)
 		if (template.Cost.PopulationBonus) ret.cost.populationBonus = +template.Cost.PopulationBonus;
 	}
 	
+	if (template.Footprint)
+	{
+		ret.footprint = {"height": template.Footprint.Height};
+		
+		if (template.Footprint.Square)
+			ret.footprint.square = {"width": +template.Footprint.Square["@width"], "depth": +template.Footprint.Square["@depth"]};
+		else if (template.Footprint.Circle)
+			ret.footprint.circle = {"radius": +template.Footprint.Circle["@radius"]};
+		else
+			warn("[GetTemplateData] Unrecognized Footprint type");
+	}
+	
+	if (template.Obstruction)
+	{
+		ret.obstruction = {
+			"active": ("" + template.Obstruction.Active == "true"),
+			"blockMovement": ("" + template.Obstruction.BlockMovement == "true"),
+			"blockPathfinding": ("" + template.Obstruction.BlockPathfinding == "true"),
+			"blockFoundation": ("" + template.Obstruction.BlockFoundation == "true"),
+			"blockConstruction": ("" + template.Obstruction.BlockConstruction == "true"),
+			"disableBlockMovement": ("" + template.Obstruction.DisableBlockMovement == "true"),
+			"disableBlockPathfinding": ("" + template.Obstruction.DisableBlockPathfinding == "true"),
+			"shape": {}
+		};
+		
+		if (template.Obstruction.Static)
+		{
+			ret.obstruction.shape.type = "static";
+			ret.obstruction.shape.width = +template.Obstruction.Static["@width"];
+			ret.obstruction.shape.depth = +template.Obstruction.Static["@depth"];
+		}
+		else
+		{
+			ret.obstruction.shape.type = "unit";
+			ret.obstruction.shape.radius = +template.Obstruction.Unit["@radius"];
+		}
+	}
+	
 	if (template.Health)
 	{
 		ret.health = +template.Health.Max;
@@ -346,6 +415,22 @@ GuiInterface.prototype.GetTemplateData = function(player, name)
 		if (template.UnitMotion.Run) ret.speed.run = +template.UnitMotion.Run.Speed;
 	}
 
+	if (template.WallSet)
+	{
+		ret.wallSet = {
+			"long": template.WallSet.WallLong,
+			"medium": template.WallSet.WallMedium,
+			"short": template.WallSet.WallShort,
+			"tower": template.WallSet.Tower,
+			"gate": template.WallSet.Gate,
+		};
+	}
+	
+	if (template.WallPiece)
+	{
+		ret.wallPiece = {"length": +template.WallPiece.Length};
+	}
+
 	return ret;
 };
 
@@ -517,6 +602,7 @@ GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)
  * Display the building placement preview.
  * cmd.template is the name of the entity template, or "" to disable the preview.
  * cmd.x, cmd.z, cmd.angle give the location.
+ * 
  * Returns true if the placement is okay (everything is valid and the entity is not obstructed by others).
  */
 GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
@@ -585,11 +671,487 @@ GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
 	return false;
 };
 
+/**
+ * Previews the placement of a wall between cmd.start and cmd.end, or just the starting piece of a wall if cmd.end is not 
+ * specified. Returns an object with information about the list of entities that need to be newly constructed to complete
+ * at least a part of the wall, or false if there are entities required to build at least part of the wall but none of
+ * them can be validly constructed.
+ * 
+ * It's important to distinguish between three lists of entities that are at play here, because they may be subsets of one
+ * another depending on things like snapping and whether some of the entities inside them can be validly positioned.
+ * We have:
+ *    - The list of entities that previews the wall. This list is usually equal to the entities required to construct the
+ *      entire wall. However, if there is snapping to an incomplete tower (i.e. a foundation), it includes extra entities
+ *      to preview the completed tower on top of its foundation.
+ *      
+ *    - The list of entities that need to be newly constructed to build the entire wall. This list is regardless of whether
+ *      any of them can be validly positioned. The emphasishere here is on 'newly'; this list does not include any existing
+ *      towers at either side of the wall that we snapped to. Or, more generally; it does not include any _entities_ that we
+ *      snapped to; we might still snap to e.g. terrain, in which case the towers on either end will still need to be newly
+ *      constructed.
+ *      
+ *    - The list of entities that need to be newly constructed to build at least a part of the wall. This list is the same
+ *      as the one above, except that it is truncated at the first entity that cannot be validly positioned. This happens
+ *      e.g. if the player tries to build a wall straight through an obstruction. Note that any entities that can be validly
+ *      constructed but come after said first invalid entity are also truncated away.
+ * 
+ * With this in mind, this method will return false if the second list is not empty, but the third one is. That is, if there
+ * were entities that are needed to build the wall, but none of them can be validly constructed. False is also returned in
+ * case of unexpected errors (typically missing components), and when clearing the preview by passing an empty wallset
+ * argument (see below). Otherwise, it will return an object with the following information:
+ * 
+ * result: {
+ *   'startSnappedEnt': ID of the entity that we snapped to at the starting side of the wall. Currently only supports towers.
+ *   'endSnappedEnt': ID of the entity that we snapped to at the (possibly truncated) ending side of the wall. Note that this
+ *                    can only be set if no truncation of the second list occurs; if we snapped to an entity at the ending side
+ *                    but the wall construction was truncated before we could reach it, it won't be set here. Currently only
+ *                    supports towers.
+ *   'pieces': Array with the following data for each of the entities in the third list:
+ *    [{
+ *       'template': Template name of the entity. 
+ *       'x': X coordinate of the entity's position.
+ *       'z': Z coordinate of the entity's position.
+ *       'angle': Rotation around the Y axis of the entity (in radians).
+ *     },
+ *     ...]
+ * }
+ * 
+ * @param cmd.wallSet Object holding the set of wall piece template names. Set to an empty value to clear the preview.
+ * @param cmd.start Starting point of the wall segment being created.
+ * @param cmd.end (Optional) Ending point of the wall segment being created. If not defined, it is understood that only
+ *                 the starting point of the wall is available at this time (e.g. while the player is still in the process
+ *                 of picking a starting point), and that therefore only the first entity in the wall (a tower) should be
+ *                 previewed.
+ * @param cmd.snapEntities List of candidate entities to snap the start and ending positions to.
+ */
+GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
+{
+	var wallSet = cmd.wallSet;
+	
+	var start = {
+		"pos": cmd.start,
+		"angle": 0,
+		"snapped": false,                       // did the start position snap to anything?
+		"snappedEnt": INVALID_ENTITY,           // if we snapped, was it to an entity? if yes, holds that entity's ID
+	};
+	
+	var end = {
+		"pos": cmd.end,
+		"angle": 0,
+		"snapped": false,                       // did the start position snap to anything?
+		"snappedEnt": INVALID_ENTITY,           // if we snapped, was it to an entity? if yes, holds that entity's ID
+	};
+	
+	// --------------------------------------------------------------------------------
+	// do some entity cache management and check for snapping
+	
+	if (!this.placementWallEntities)
+		this.placementWallEntities = {};
+	
+	if (!wallSet)
+	{
+		// we're clearing the preview, clear the entity cache and bail
+		var numCleared = 0;
+		for (var tpl in this.placementWallEntities)
+		{
+			for each (var ent in this.placementWallEntities[tpl].entities)
+				Engine.DestroyEntity(ent);
+			
+			this.placementWallEntities[tpl].numUsed = 0;
+			this.placementWallEntities[tpl].entities = [];
+			// keep template data around
+		}
+		
+		return false;
+	}
+	else
+	{
+		// Move all existing cached entities outside of the world and reset their use count
+		for (var tpl in this.placementWallEntities)
+		{
+			for each (var ent in this.placementWallEntities[tpl].entities)
+			{
+				var pos = Engine.QueryInterface(ent, IID_Position);
+				if (pos)
+					pos.MoveOutOfWorld();
+			}
+			
+			this.placementWallEntities[tpl].numUsed = 0;
+		}
+		
+		// Create cache entries for templates we haven't seen before
+		for each (var tpl in wallSet)
+		{
+			if (!(tpl in this.placementWallEntities))
+			{
+				//warn("[SetWallPlacementPreview] Initializing wall data for '" + tpl + "'");
+	    		this.placementWallEntities[tpl] = {
+	    			"numUsed": 0,
+	    			"entities": [],
+	    			"templateData": this.GetTemplateData(player, tpl),
+	    		};
+	    		
+	    		// ensure that the loaded template data contains a wallPiece component
+	    		if (!this.placementWallEntities[tpl].templateData.wallPiece)
+	    		{
+	    			error("[SetWallPlacementPreview] No WallPiece component found for wall set template '" + tpl + "'");
+	    			return false;
+	    		}
+			}
+		}
+	}
+		
+	
+	// prevent division by zero errors further on if the start and end positions are the same
+	if (end.pos && (start.pos.x === end.pos.x && start.pos.z === end.pos.z))
+		end.pos = undefined;
+	
+	// See if we need to snap the start and/or end coordinates to any of our list of snap entities. Note that, despite the list
+	// of snapping candidate entities, it might still snap to e.g. terrain features. Use the "ent" key in the returned snapping
+	// data (if any) to determine whether it snapped to an entity, and to which one (see GetFoundationSnapData).
+	if (cmd.snapEntities)
+	{
+		var snapRadius = this.placementWallEntities[wallSet.tower].templateData.wallPiece.length * 0.5; // determined through trial and error
+		var startSnapData = this.GetFoundationSnapData(player, {
+    		"x": start.pos.x,
+    		"z": start.pos.z,
+    		"template": wallSet.tower,
+    		"snapEntities": cmd.snapEntities,
+    		"snapRadius": snapRadius,
+    	});
+    	
+    	if (startSnapData)
+    	{
+    		start.pos.x = startSnapData.x;
+    		start.pos.z = startSnapData.z;
+    		start.angle = startSnapData.angle;
+    		start.snapped = true;
+    		
+    		if (startSnapData.ent)
+    			start.snappedEnt = startSnapData.ent; 
+    	}
+	
+    	if (end.pos)
+    	{
+    		var endSnapData = this.GetFoundationSnapData(player, {
+    			"x": end.pos.x,
+    			"z": end.pos.z,
+    			"template": wallSet.tower,
+    			"snapEntities": cmd.snapEntities,
+    			"snapRadius": snapRadius,
+    		});
+    		
+    		if (endSnapData)
+    		{
+    			end.pos.x = endSnapData.x;
+    			end.pos.z = endSnapData.z;
+    			end.angle = endSnapData.angle;
+    			end.snapped = true;
+    			
+    			if (endSnapData.ent)
+    				end.snappedEnt = endSnapData.ent;
+    		}
+    	}
+	}
+	
+	// clear the single-building preview entity (we'll be rolling our own)
+	this.SetBuildingPlacementPreview(player, {"template": ""});
+	
+	// --------------------------------------------------------------------------------
+	// calculate wall placement and position preview entities
+	
+	var result = {"pieces": []};
+	
+	var previewEntities = [];
+	if (end.pos)
+		previewEntities = GetWallPlacement(this.placementWallEntities, wallSet, start, end); // see helpers/Walls.js
+	
+	// For wall placement, we may (and usually do) need to have wall pieces overlap each other more than would 
+	// otherwise be allowed by their obstruction shapes. However, during this preview phase, this is not so much of
+	// an issue, because all preview entities have their obstruction components deactivated, meaning that their 
+	// obstruction shapes do not register in the simulation and hence cannot affect it. This means that the preview
+	// entities cannot be found to obstruct each other, which largely solves the issue of overlap between wall pieces.
+	
+	// Note that they will still be obstructed by existing shapes in the simulation (that have the BLOCK_FOUNDATION
+	// flag set), which is what we want. The only exception to this is when snapping to existing towers (or
+	// foundations thereof); the wall segments that connect up to these will be found to be obstructed by the
+	// existing tower/foundation and be shaded red to indicate that they cannot be placed there. To prevent this,
+	// we manually set the control group of the outermost wall pieces equal to those of the snapped-to towers, so
+	// that they are free from mutual obstruction (per definition of obstruction control groups). This is done by
+	// assigning them an extra "controlGroup" field, which we'll then set during the placement loop below.
+	
+	// Additionally, in the situation that we're snapping to merely a foundation of a tower instead of a fully
+	// constructed one, we'll need an extra preview entity for the starting tower, which also must not be obstructed
+	// by the foundation it snaps to.
+	
+	if (start.snappedEnt && start.snappedEnt != INVALID_ENTITY)
+	{
+		var startEntObstruction = Engine.QueryInterface(start.snappedEnt, IID_Obstruction);
+		if (previewEntities.length > 0 && startEntObstruction)
+			previewEntities[0].controlGroup = startEntObstruction.GetControlGroup();
+		
+		// if we're snapping to merely a foundation, add an extra preview tower and also set it to the same control group
+		var startEntState = this.GetEntityState(player, start.snappedEnt);
+		if (startEntState.foundation)
+		{
+			var cmpPosition = Engine.QueryInterface(start.snappedEnt, IID_Position);
+			if (cmpPosition)
+			{
+				previewEntities.unshift({
+					"template": wallSet.tower,
+					"pos": start.pos,
+					"angle": cmpPosition.GetRotation().y,
+					"controlGroup": (startEntObstruction ? startEntObstruction.GetControlGroup() : undefined),
+					"excludeFromResult": true,
+				});
+			}
+		}
+	}
+	else
+	{
+		// Didn't snap to an existing entity, add the starting tower manually. Reuse the placement angle that was last
+		// seen on a validly positioned wall piece, for better visual consistency and to prevent odd-looking jumps in 
+		// rotation when shift-clicking to build a wall.
+		
+		// (Consider what happens if we used a constant of, say, 0 instead. Issuing the build command for a wall is
+		// asynchronous, so when the preview updates after shift-clicking, the foundation is typically not there yet in
+		// the simulation. This means they cannot possibly be picked in the list of candidate entities for snapping, and
+		// so we hit this case and rotate the preview to 0 radians. Then, after one or two simulation updates or so, the
+		// foundation registers and onSimulationUpdate in session.js updates the preview again. It first grabs a new list
+		// of snapping candidates, which this time does contain the new foundations; so we snap to the entity, and rotate
+		// the preview back to the foundation's angle. The result is a noticeable rotation to 0 and back, which is
+		// undesirable. So, for a split second there until the simulation updates, we fake it by reusing the last angle,
+		// and hope the player doesn't notice).
+		previewEntities.unshift({
+			"template": wallSet.tower,
+			"pos": start.pos,
+			"angle": (previewEntities.length > 0 ? previewEntities[0].angle : this.placementWallLastAngle)
+		});
+	}
+	
+	if (end.pos)
+	{
+		// Analogous to the starting side case above
+		if (end.snappedEnt && end.snappedEnt != INVALID_ENTITY)
+		{
+			var endEntObstruction = Engine.QueryInterface(end.snappedEnt, IID_Obstruction);
+			
+			// TODO: technically it's possible (but highly unlikely) for the last entity in previewEntities to be the same as the first,
+			// i.e. the same wall piece snapping to both a starting and an ending tower. For this to happen, the wall piece would have to
+			// be exactly as long as the distance between start.pos and end.pos, but it's possible, so we should probably turn
+			// '.controlGroup' into '.controlGroups' and append to it, and assign them to the primary and secondary control groups if there
+			// are two (there can only be 0, 1 or 2).
+			if (previewEntities.length > 0 && endEntObstruction)
+				previewEntities[previewEntities.length-1].controlGroup = endEntObstruction.GetControlGroup();
+			
+			// if we're snapping to a foundation, add an extra preview tower and also set it to the same control group
+    		var endEntState = this.GetEntityState(player, end.snappedEnt);
+    		if (endEntState.foundation)
+    		{
+    			var cmpPosition = Engine.QueryInterface(end.snappedEnt, IID_Position);
+    			if (cmpPosition)
+    			{
+    				previewEntities.push({
+    					"template": wallSet.tower,
+    					"pos": end.pos,
+    					"angle": cmpPosition.GetRotation().y,
+    					"controlGroup": (endEntObstruction ? endEntObstruction.GetControlGroup() : undefined),
+    					"excludeFromResult": true
+    				});
+    			}
+    		}
+		}
+		else
+		{
+			previewEntities.push({
+				"template": wallSet.tower,
+				"pos": end.pos,
+				"angle": (previewEntities.length > 0 ? previewEntities[previewEntities.length-1].angle : this.placementWallLastAngle)
+			});
+		}
+	}
+	
+	// Loop through the preview entities, and construct the subset of them that need to be, and can be, validly constructed to build at
+	// least a part of the wall (meaning that the subset is truncated after the first entity that needs to be, but cannot validly be,
+	// constructed).
+	
+	var allPiecesValid = true;
+	var numRequiredPieces = 0; // number of entities that are required to build the entire wall, regardless of validity
+	
+	for (var i = 0; i < previewEntities.length; ++i)
+	{
+		var entInfo = previewEntities[i];
+		
+		var ent = null;
+		var tpl = entInfo.template;
+		var entPool = this.placementWallEntities[tpl];
+		
+		if (entPool.numUsed >= entPool.entities.length)
+		{
+			// allocate new entity
+			//warn("Allocating new entity of template '" + tpl + "'");
+			ent = Engine.AddLocalEntity("preview|" + tpl);
+			entPool.entities.push(ent);
+		}
+		else
+		{
+			 // reuse an existing one
+			ent = entPool.entities[entPool.numUsed];
+		}
+		
+		if (!ent)
+		{
+			error("[SetWallPlacementPreview] Failed to allocate or reuse preview entity of template '" + tpl + "'");
+			continue;
+		}
+		
+		// move piece to right location
+		// TODO: reuse SetBuildingPlacementReview for this, multiplexed to be able to deal with multiple entities?
+		
+		var cmpPosition = Engine.QueryInterface(ent, IID_Position);
+		if (cmpPosition)
+		{
+			cmpPosition.JumpTo(entInfo.pos.x, entInfo.pos.z);
+			cmpPosition.SetYRotation(entInfo.angle);
+		}
+		
+		var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
+		if (!cmpObstruction)
+		{
+			error("[SetWallPlacementPreview] Preview entity of template " + tpl + " does not have an obstruction component!");
+			continue;
+		}
+		
+		if (entInfo.controlGroup)
+		{
+			cmpObstruction.SetControlGroup(entInfo.controlGroup);
+		}
+		else
+		{
+			// Reset the control group (remember, we're reusing entities; without this, an ending wall segment that was 
+			// at one point snapped to an existing tower, and is subsequently reused to be the same ending segment (only 
+			// this time not snapped), would no longer be capable of being obstructed by the same tower)
+			cmpObstruction.SetControlGroup(ent);
+		}
+		
+		// check whether this wall piece can be validly positioned here
+		var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
+		if (!cmpBuildRestrictions)
+		{
+			error("[SetWallPlacementPreview] cmpBuildRestrictions not defined for template '" + tpl + "'");
+			continue;
+		}
+		
+		var validPlacement = (cmpBuildRestrictions && cmpBuildRestrictions.CheckPlacement(player));
+		allPiecesValid = allPiecesValid && validPlacement;
+		
+		var cmpVisual = Engine.QueryInterface(ent, IID_Visual);
+		if (cmpVisual)
+		{
+			if (!validPlacement)
+				cmpVisual.SetShadingColour(1.4, 0.4, 0.4, 1);
+			else
+				cmpVisual.SetShadingColour(1, 1, 1, 1);
+		}
+		
+		// The requirement that all pieces so far have to have valid positions, rather than only this single one,
+		// ensures that no more foundations will be placed after a first invalidly-positioned piece. (It is possible
+		// for pieces past some invalidly-positioned ones to still have valid positions, e.g. if you drag a wall 
+		// through and past an existing building).
+		
+		// Additionally, the excludeFromResult flag is set for preview entities that were manually added to be placed
+		// on top of foundations of incompleted towers that we snapped to; they must not be part of the result.
+		
+		if (!entInfo.excludeFromResult)
+			numRequiredPieces++;
+		
+		if (allPiecesValid && !entInfo.excludeFromResult)
+		{
+			result.pieces.push({
+				"template": tpl,
+				"x": entInfo.pos.x,
+				"z": entInfo.pos.z,
+				"angle": entInfo.angle,
+			});
+			this.placementWallLastAngle = entInfo.angle;
+		}
+		
+		entPool.numUsed++;
+	}
+	
+	// If any were entities required to build the wall, but none of them could be validly positioned, return failure
+	// (see method documentation).
+	if (numRequiredPieces > 0 && result.pieces.length <= 0)
+		return false;
+	
+	if (start.snappedEnt && start.snappedEnt != INVALID_ENTITY)
+		result.startSnappedEnt = start.snappedEnt;
+	
+	// We should only return that we snapped to an entity if all pieces up until that entity can be validly constructed,
+	// i.e. are included in result.pieces (see docs for the result object).
+	if (end.pos && end.snappedEnt && end.snappedEnt != INVALID_ENTITY && allPiecesValid)
+		result.endSnappedEnt = end.snappedEnt;
+	
+	return result;
+};
+
+/**
+ * Given the current position {data.x, data.z} of an foundation of template data.template, returns the position and angle to snap
+ * it to (if necessary/useful).
+ * 
+ * @param data.x            The X position of the foundation to snap.
+ * @param data.z            The Z position of the foundation to snap.
+ * @param data.template     The template to get the foundation snapping data for.
+ * @param data.snapEntities Optional; list of entity IDs to snap to if {data.x, data.z} is within a circle of radius data.snapRadius
+ *                            around the entity. Only takes effect when used in conjunction with data.snapRadius.
+ *                          When this option is used and the foundation is found to snap to one of the entities passed in this list
+ *                            (as opposed to e.g. snapping to terrain features), then the result will contain an additional key "ent",
+ *                            holding the ID of the entity that was snapped to.
+ * @param data.snapRadius   Optional; when used in conjunction with data.snapEntities, indicates the circle radius around an entity that
+ *                            {data.x, data.z} must be located within to have it snap to that entity.
+ */
 GuiInterface.prototype.GetFoundationSnapData = function(player, data)
 {
 	var cmpTemplateMgr = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
 	var template = cmpTemplateMgr.GetTemplate(data.template);
 
+	if (!template)
+	{
+		warn("[GetFoundationSnapData] Failed to load template '" + data.template + "'");
+		return false;
+	}
+	
+	if (data.snapEntities && data.snapRadius && data.snapRadius > 0)
+	{
+		// see if {data.x, data.z} is inside the snap radius of any of the snap entities; and if so, to which it is closest
+		// (TODO: break unlikely ties by choosing the lowest entity ID)
+		
+		var minDist2 = -1;
+		var minDistEntitySnapData = null;
+		var radius2 = data.snapRadius * data.snapRadius;
+		
+		for each (ent in data.snapEntities)
+		{
+			var cmpPosition = Engine.QueryInterface(ent, IID_Position);
+			if (!cmpPosition || !cmpPosition.IsInWorld())
+				continue;
+			
+			var pos = cmpPosition.GetPosition();
+			var dist2 = (data.x - pos.x) * (data.x - pos.x) + (data.z - pos.z) * (data.z - pos.z);
+			if (dist2 > radius2)
+				continue;
+			
+			if (minDist2 < 0 || dist2 < minDist2)
+			{
+				minDist2 = dist2;
+				minDistEntitySnapData = {"x": pos.x, "z": pos.z, "angle": cmpPosition.GetRotation().y, "ent": ent};
+			}
+		}
+		
+		if (minDistEntitySnapData != null)
+			return minDistEntitySnapData;
+	}
+	
 	if (template.BuildRestrictions.Category == "Dock")
 	{
 		var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
@@ -813,6 +1375,7 @@ var exposedFunctions = {
 	"SetStatusBars": 1,
 	"DisplayRallyPoint": 1,
 	"SetBuildingPlacementPreview": 1,
+	"SetWallPlacementPreview": 1,
 	"GetFoundationSnapData": 1,
 	"PlaySound": 1,
 	"FindIdleUnit": 1,
diff --git a/binaries/data/mods/public/simulation/components/WallPiece.js b/binaries/data/mods/public/simulation/components/WallPiece.js
new file mode 100644
index 0000000..f55f4d0
--- /dev/null
+++ b/binaries/data/mods/public/simulation/components/WallPiece.js
@@ -0,0 +1,16 @@
+function WallPiece() {}
+
+WallPiece.prototype.Schema =
+	"<a:help></a:help>" +
+	"<a:example>" +
+	"</a:example>" +
+	"<element name='Length'>" +
+		"<ref name='nonNegativeDecimal'/>" +
+    "</element>";
+
+
+WallPiece.prototype.Init = function()
+{
+};
+
+Engine.RegisterComponentType(IID_WallPiece, "WallPiece", WallPiece);
diff --git a/binaries/data/mods/public/simulation/components/WallSet.js b/binaries/data/mods/public/simulation/components/WallSet.js
new file mode 100644
index 0000000..79b673b
--- /dev/null
+++ b/binaries/data/mods/public/simulation/components/WallSet.js
@@ -0,0 +1,32 @@
+function WallSet() {}
+
+WallSet.prototype.Schema =
+	"<a:help></a:help>" +
+	"<a:example>" +
+	"</a:example>" +
+	"<interleave>" +
+    	"<element name='WallLong'>" +
+        	"<text/>" +
+        "</element>" +
+        "<element name='WallMedium'>" +
+        	"<text/>" +
+        "</element>" +
+        "<element name='WallShort'>" +
+        	"<text/>" +
+        "</element>" +
+		"<element name='Tower'>" +
+			"<text/>" +
+		"</element>" +
+		"<element name='Gate'>" +
+    		"<text/>" +
+    	"</element>" +
+	"</interleave>";
+
+
+WallSet.prototype.Init = function()
+{
+};
+
+WallSet.prototype.Serialize = null;
+
+Engine.RegisterComponentType(IID_WallSet, "WallSet", WallSet);
diff --git a/binaries/data/mods/public/simulation/components/interfaces/WallPiece.js b/binaries/data/mods/public/simulation/components/interfaces/WallPiece.js
new file mode 100644
index 0000000..ad4ae36
--- /dev/null
+++ b/binaries/data/mods/public/simulation/components/interfaces/WallPiece.js
@@ -0,0 +1 @@
+Engine.RegisterInterface("WallPiece");
\ No newline at end of file
diff --git a/binaries/data/mods/public/simulation/components/interfaces/WallSet.js b/binaries/data/mods/public/simulation/components/interfaces/WallSet.js
new file mode 100644
index 0000000..10e0cfc
--- /dev/null
+++ b/binaries/data/mods/public/simulation/components/interfaces/WallSet.js
@@ -0,0 +1 @@
+Engine.RegisterInterface("WallSet");
\ No newline at end of file
diff --git a/binaries/data/mods/public/simulation/helpers/Commands.js b/binaries/data/mods/public/simulation/helpers/Commands.js
index 4d07c2e..0724af0 100644
--- a/binaries/data/mods/public/simulation/helpers/Commands.js
+++ b/binaries/data/mods/public/simulation/helpers/Commands.js
@@ -8,12 +8,15 @@ function ProcessCommand(player, cmd)
 	var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
 	if (!cmpPlayerMan || player < 0)
 		return;
+	
 	var playerEnt = cmpPlayerMan.GetPlayerByID(player);
 	if (playerEnt == INVALID_ENTITY)
 		return;
+	
 	var cmpPlayer = Engine.QueryInterface(playerEnt, IID_Player);
 	if (!cmpPlayer)
 		return;
+	
 	var controlAllUnits = cmpPlayer.CanControlAllUnits();
 
 	// Note: checks of UnitAI targets are not robust enough here, as ownership
@@ -146,135 +149,11 @@ function ProcessCommand(player, cmd)
 		break;
 
 	case "construct":
-		// Message structure:
-		// {
-		//   "type": "construct",
-		//   "entities": [...],
-		//   "template": "...",
-		//   "x": ...,
-		//   "z": ...,
-		//   "angle": ...,
-		//   "autorepair": true, // whether to automatically start constructing/repairing the new foundation
-		//   "autocontinue": true, // whether to automatically gather/build/etc after finishing this
-		//   "queued": true,
-		// }
-
-		/*
-		 * Construction process:
-		 *  . Take resources away immediately.
-		 *  . Create a foundation entity with 1hp, 0% build progress.
-		 *  . Increase hp and build progress up to 100% when people work on it.
-		 *  . If it's destroyed, an appropriate fraction of the resource cost is refunded.
-		 *  . If it's completed, it gets replaced with the real building.
-		 */
-		 
-		// Check that we can control these units
-		var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
-		if (!entities.length)
-			break;
-
-		// Tentatively create the foundation (we might find later that it's a invalid build command)
-		var ent = Engine.AddEntity("foundation|" + cmd.template);
-		if (ent == INVALID_ENTITY)
-		{
-			// Error (e.g. invalid template names)
-			error("Error creating foundation entity for '" + cmd.template + "'");
-			break;
-		}
-
-		// Move the foundation to the right place
-		var cmpPosition = Engine.QueryInterface(ent, IID_Position);
-		cmpPosition.JumpTo(cmd.x, cmd.z);
-		cmpPosition.SetYRotation(cmd.angle);
-
-		// Check whether it's obstructed by other entities or invalid terrain
-		var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
-		if (!cmpBuildRestrictions || !cmpBuildRestrictions.CheckPlacement(player))
-		{
-			if (g_DebugCommands)
-			{
-				warn("Invalid command: build restrictions check failed for player "+player+": "+uneval(cmd));
-			}
-
-			var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
-			cmpGuiInterface.PushNotification({ "player": player, "message": "Building site was obstructed" });
-
-			// Remove the foundation because the construction was aborted
-			Engine.DestroyEntity(ent);
-			break;
-		}
-
-		// Check build limits
-		var cmpBuildLimits = QueryPlayerIDInterface(player, IID_BuildLimits);
-		if (!cmpBuildLimits || !cmpBuildLimits.AllowedToBuild(cmpBuildRestrictions.GetCategory()))
-		{
-			if (g_DebugCommands)
-			{
-				warn("Invalid command: build limits check failed for player "+player+": "+uneval(cmd));
-			}
-
-			// TODO: The UI should tell the user they can't build this (but we still need this check)
-
-			// Remove the foundation because the construction was aborted
-			Engine.DestroyEntity(ent);
-			break;
-		}
-
-		// TODO: AI has no visibility info
-		if (!cmpPlayer.IsAI())
-		{
-			// Check whether it's in a visible or fogged region
-			//	tell GetLosVisibility to force RetainInFog because preview entities set this to false,
-			//	which would show them as hidden instead of fogged
-			var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
-			var visible = (cmpRangeManager && cmpRangeManager.GetLosVisibility(ent, player, true) != "hidden");
-			if (!visible)
-			{
-				if (g_DebugCommands)
-				{
-					warn("Invalid command: foundation visibility check failed for player "+player+": "+uneval(cmd));
-				}
-
-				var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
-				cmpGuiInterface.PushNotification({ "player": player, "message": "Building site was not visible" });
-
-				Engine.DestroyEntity(ent);
-				break;
-			}
-		}
-
-		var cmpCost = Engine.QueryInterface(ent, IID_Cost);
-		if (!cmpPlayer.TrySubtractResources(cmpCost.GetResourceCosts()))
-		{
-			if (g_DebugCommands)
-			{
-				warn("Invalid command: building cost check failed for player "+player+": "+uneval(cmd));
-			}
-
-			Engine.DestroyEntity(ent);
-			break;
-		}
-
-		// Make it owned by the current player
-		var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
-		cmpOwnership.SetOwner(player);
-
-		// Initialise the foundation
-		var cmpFoundation = Engine.QueryInterface(ent, IID_Foundation);
-		cmpFoundation.InitialiseConstruction(player, cmd.template);
-
-		// Tell the units to start building this new entity
-		if (cmd.autorepair)
-		{
-			ProcessCommand(player, {
-				"type": "repair",
-				"entities": entities,
-				"target": ent,
-				"autocontinue": cmd.autocontinue,
-				"queued": cmd.queued
-			});
-		}
+		TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd);
+		break;
 
+	case "construct-wall":
+		TryConstructWall(player, cmpPlayer, controlAllUnits, cmd);
 		break;
 
 	case "delete-entities":
@@ -467,6 +346,437 @@ function ExtractFormations(ents)
 }
 
 /**
+ * Attempts to construct a building using the specified parameters.
+ * Returns true on success, false on failure.
+ */
+function TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd)
+{
+	// Message structure:
+	// {
+	//   "type": "construct",
+	//   "entities": [...],                 // entities that will be ordered to construct the building (if applicable)
+	//   "template": "...",                 // template name of the entity being constructed
+	//   "x": ...,
+	//   "z": ...,
+	//   "angle": ...,
+	//   "autorepair": true,                // whether to automatically start constructing/repairing the new foundation
+	//   "autocontinue": true,              // whether to automatically gather/build/etc after finishing this
+	//   "queued": true,                    // whether to add the construction/repairing of this foundation to entities' queue (if applicable)
+	//   "obstructionControlGroup": ...,    // Optional; the obstruction control group ID that should be set for this building prior to obstruction
+	//                                      // testing to determine placement validity. If specified, must be a valid control group ID (> 0).
+	//   "obstructionControlGroup2": ...,   // Optional; secondary obstruction control group ID that should be set for this building prior to obstruction
+	//                                      // testing to determine placement validity. May be INVALID_ENTITY.
+	// }
+	
+	/*
+	 * Construction process:
+	 *  . Take resources away immediately.
+	 *  . Create a foundation entity with 1hp, 0% build progress.
+	 *  . Increase hp and build progress up to 100% when people work on it.
+	 *  . If it's destroyed, an appropriate fraction of the resource cost is refunded.
+	 *  . If it's completed, it gets replaced with the real building.
+	 */
+	
+	// Check whether we can control these units
+	var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
+	if (!entities.length)
+		return false;
+	
+	// Tentatively create the foundation (we might find later that it's a invalid build command)
+	var ent = Engine.AddEntity("foundation|" + cmd.template);
+	if (ent == INVALID_ENTITY)
+	{
+		// Error (e.g. invalid template names)
+		error("Error creating foundation entity for '" + cmd.template + "'");
+		return false;
+	}
+	
+	// Move the foundation to the right place
+	var cmpPosition = Engine.QueryInterface(ent, IID_Position);
+	cmpPosition.JumpTo(cmd.x, cmd.z);
+	cmpPosition.SetYRotation(cmd.angle);
+	
+	// Set the obstruction control group if needed
+	if (cmd.obstructionControlGroup || cmd.obstructionControlGroup2)
+	{
+		var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
+		
+		// primary control group must always be valid
+		if (cmd.obstructionControlGroup)
+		{
+			if (cmd.obstructionControlGroup <= 0)
+				warn("[TryConstructBuilding] Invalid primary obstruction control group " + cmd.obstructionControlGroup + " received; must be > 0");
+			
+			cmpObstruction.SetControlGroup(cmd.obstructionControlGroup);
+		}
+		
+		if (cmd.obstructionControlGroup2)
+			cmpObstruction.SetControlGroup2(cmd.obstructionControlGroup2);
+	}
+	
+	// Check whether it's obstructed by other entities or invalid terrain
+	var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
+	if (!cmpBuildRestrictions || !cmpBuildRestrictions.CheckPlacement(player))
+	{
+		if (g_DebugCommands)
+		{
+			warn("Invalid command: build restrictions check failed for player "+player+": "+uneval(cmd));
+		}
+		
+		var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
+		cmpGuiInterface.PushNotification({ "player": player, "message": "Building site was obstructed" });
+		
+		// Remove the foundation because the construction was aborted
+		Engine.DestroyEntity(ent);
+		return false;
+	}
+	
+	// Check build limits
+	var cmpBuildLimits = QueryPlayerIDInterface(player, IID_BuildLimits);
+	if (!cmpBuildLimits || !cmpBuildLimits.AllowedToBuild(cmpBuildRestrictions.GetCategory()))
+	{
+		if (g_DebugCommands)
+		{
+			warn("Invalid command: build limits check failed for player "+player+": "+uneval(cmd));
+		}
+		
+		// TODO: The UI should tell the user they can't build this (but we still need this check)
+		
+		// Remove the foundation because the construction was aborted
+		Engine.DestroyEntity(ent);
+		return false;
+	}
+	
+	// TODO: AI has no visibility info
+	if (!cmpPlayer.IsAI())
+	{
+		// Check whether it's in a visible or fogged region
+		//	tell GetLosVisibility to force RetainInFog because preview entities set this to false,
+		//	which would show them as hidden instead of fogged
+		var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+		var visible = (cmpRangeManager && cmpRangeManager.GetLosVisibility(ent, player, true) != "hidden");
+		if (!visible)
+		{
+			if (g_DebugCommands)
+			{
+				warn("Invalid command: foundation visibility check failed for player "+player+": "+uneval(cmd));
+			}
+			
+			var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
+			cmpGuiInterface.PushNotification({ "player": player, "message": "Building site was not visible" });
+			
+			Engine.DestroyEntity(ent);
+			return false;
+		}
+	}
+	
+	var cmpCost = Engine.QueryInterface(ent, IID_Cost);
+	if (!cmpPlayer.TrySubtractResources(cmpCost.GetResourceCosts()))
+	{
+		if (g_DebugCommands)
+		{
+			warn("Invalid command: building cost check failed for player "+player+": "+uneval(cmd));
+		}
+		
+		Engine.DestroyEntity(ent);
+		return false;
+	}
+	
+	// Make it owned by the current player
+	var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
+	cmpOwnership.SetOwner(player);
+	
+	// Initialise the foundation
+	var cmpFoundation = Engine.QueryInterface(ent, IID_Foundation);
+	cmpFoundation.InitialiseConstruction(player, cmd.template);
+	
+	// Tell the units to start building this new entity
+	if (cmd.autorepair)
+	{
+		ProcessCommand(player, {
+			"type": "repair",
+			"entities": entities,
+			"target": ent,
+			"autocontinue": cmd.autocontinue,
+			"queued": cmd.queued
+		});
+	}
+	
+	return ent;
+}
+
+function TryConstructWall(player, cmpPlayer, controlAllUnits, cmd)
+{
+	// Message structure:
+	// {
+	//   "type": "construct-wall",
+	//   "entities": [...],    // entities that will be ordered to construct the wall (if applicable)
+	//   "pieces": [
+	//      {
+	//          "template": "...",    // template name of the wall piece entity being constructed (e.g. towers, long/short/med segments, ...)
+	//          "x": ...,
+	//          "z": ...,
+	//          "angle": ...,
+	//      },
+	//      ...
+	//   ],
+	//   "wallSet": {
+	//      "tower": [tower template name],
+	//      "long":  [long wall segment template name],
+	//      ...
+	//      (TODO: we only really need the tower template name here)
+	//   },
+	//   "startSnappedEntity":    // optional; entity ID of tower being snapped to at the starting side of the wall
+	//   "endSnappedEntity":      // optional; entity ID of tower being snapped to at the ending side of the wall
+	//   "autorepair": true,      // whether to automatically start constructing/repairing the new foundation
+	//   "autocontinue": true,    // whether to automatically gather/build/etc after finishing this
+	//   "queued": true,          // whether to add the construction/repairing of this wall's pieces to entities' queue (if applicable)
+	// }
+	
+	if (cmd.pieces.length <= 0)
+		return;
+	
+	if (cmd.startSnappedEntity && cmd.pieces[0].template == cmd.wallSet.tower)
+	{
+		error("[TryConstructWall] Starting wall piece cannot be a tower (" + cmd.wallSet.tower + ") when snapping at the starting side");
+		return;
+	}
+	
+	if (cmd.endSnappedEntity && cmd.pieces[cmd.pieces.length - 1].template == cmd.wallSet.tower)
+	{
+		error("[TryConstructWall] Ending wall piece cannot be a tower (" + cmd.wallSet.tower + ") when snapping at the ending side");
+		return;
+	}
+	
+	// Assign obstruction control groups to allow the wall pieces to mutually overlap during foundation placement
+	// and during construction. The scheme here is that whatever wall pieces are inbetween two towers inherit the control 
+	// groups of both of the towers they are connected to (either newly constructed ones as part of the wall, or existing
+	// towers in the case of snapping). The towers themselves all keep their default unique control groups.
+	
+	// To support this, every non-tower piece registers the entity ID of the tower foundations that neighbour it on either side.
+	// We can't build the whole wall at once by linearly stepping through the wall pieces and build them, because the 
+	// wall segments may/will need the entity IDs of towers that come afterwards.
+	
+	// So, build it in two passes:
+	//    - In the first pass, go left to right (i.e. from start to end position), and construct wall piece foundations as
+	//        far as we can without running into a piece that cannot be built (e.g. because it is obstructed). At each step,
+	//        set the left-neighbouring tower ID as the primary control group, thus allowing it to be built overlapping it.
+	//      If we encounter a new tower along the way (which will gain its own control group), first build it using temporarily
+	//        the same control group of the previous (non-tower) piece, then set the previous piece's secondary control group
+	//        to the tower's entity ID, and then restore the primary control group of the constructed tower back its original
+	//        (unique) value. (If we hadn't first temporarily given it the previous piece's control group, it wouldn't've been
+	//        able to be placed while overlapping the previous piece).
+	//      If we hit a piece that can't be built, backtrack to the last tower that was successfully built, deleting any 
+	//        non-tower pieces that were built after it (we want walls to always end in a tower so we can always extend it 
+	//        by snapping onto it).
+	//      
+	//    - In the second pass, go right to left from said last successfully placed wall piece (which might be a tower we
+	//      backtracked to), this time registering the right neighbouring tower in each non-tower piece.
+	
+	// first pass; L -> R
+	
+	var lastTowerIndex = -1; // index of the last tower we've encountered in cmd.pieces
+	var lastTowerControlGroup = null; // control group of the last tower we've encountered, to assign to non-tower pieces
+	
+	if (cmd.startSnappedEntity)
+	{
+		var cmpSnappedStartObstruction = Engine.QueryInterface(cmd.startSnappedEntity, IID_Obstruction);
+		if (!cmpSnappedStartObstruction)
+		{
+			error("[TryConstructWall] Snapped entity on starting side does not have an obstruction component");
+			return;
+		}
+		
+		lastTowerControlGroup = cmpSnappedStartObstruction.GetControlGroup();
+		//warn("setting lastTowerControlGroup to control group of start snapped entity " + cmd.startSnappedEntity + ": " + lastTowerControlGroup);
+	}
+	
+	var i = 0;
+	for (; i < cmd.pieces.length; ++i)
+	{
+		var piece = cmd.pieces[i];
+		
+		//warn("---[>>]--- iteration " + i);
+		
+		// except for if we don't do start position snapping and the first entity we build is therefore a tower,
+		// 'lastTowerControlGroup' must always be defined and valid here
+		if (lastTowerControlGroup === null || lastTowerControlGroup == INVALID_ENTITY)
+		{
+			if (!(i == 0 && piece.template == cmd.wallSet.tower && !cmd.startSnappedEntity))
+			{
+    			error("[TryConstructWall] Expected last tower control group to be available, none found (1st pass)");
+    			break;
+			}
+		}
+		
+		var constructPieceCmd = {
+			"type": "construct",
+			"entities": cmd.entities,
+			"template": piece.template,
+			"x": piece.x,
+			"z": piece.z,
+			"angle": piece.angle,
+			"autorepair": cmd.autorepair,
+			"autocontinue": cmd.autocontinue,
+			"queued": cmd.queued,
+			// regardless of whether we're building a tower or an intermediate wall piece, it is always (first) constructed
+			// using the control group of the last tower (see algorithm outline above)
+			"obstructionControlGroup": lastTowerControlGroup, // allowed to be null; should be the case when the first entity is a tower
+		};
+		
+		// if we're building the last piece and we're attaching to a snapped entity, we need to add in the snapped entity's
+		// control group directly at construction time (instead of setting it in the second pass) to allow it to be built with
+		// overlap to the snapped entity
+		if (cmd.endSnappedEntity && i == cmd.pieces.length - 1)
+		{
+			var cmpEndSnappedObstruction = Engine.QueryInterface(cmd.endSnappedEntity, IID_Obstruction);
+			// TODO: ensure cmpEndSnappedObstruction exists
+			constructPieceCmd.obstructionControlGroup2 = cmpEndSnappedObstruction.GetControlGroup();
+		}
+		
+		//for (var k in constructPieceCmd)
+		//	warn("   " + k + ": " + uneval(constructPieceCmd[k]));
+		//warn("   ");
+		
+		var pieceEntityId = TryConstructBuilding(player, cmpPlayer, controlAllUnits, constructPieceCmd);
+		if (pieceEntityId)
+		{
+			//warn("   successfully built wall piece; ID = " + pieceEntityId);
+			
+			// wall piece foundation successfully built, save the entity ID in the piece info object so we can reference it later
+			piece.ent = pieceEntityId;
+			
+			// if we built a tower, do the control group dance (see algorithm outline above) and update lastTowerControlGroup 
+			// and lastTowerIndex
+			if (piece.template == cmd.wallSet.tower)
+			{
+				var cmpTowerObstruction = Engine.QueryInterface(pieceEntityId, IID_Obstruction);
+				var newTowerControlGroup = pieceEntityId;
+				
+				if (i > 0)
+				{
+					//warn("   updating previous wall piece's secondary control group to " + newTowerControlGroup);
+					var cmpPreviousObstruction = Engine.QueryInterface(cmd.pieces[i-1].ent, IID_Obstruction);
+					// TODO: ensure that cmpPreviousObstruction exists
+					// TODO: ensure that the previous obstruction does not yet have a secondary control group set
+					cmpPreviousObstruction.SetControlGroup2(newTowerControlGroup);
+				}
+				
+				// TODO: ensure that cmpTowerObstruction exists
+				cmpTowerObstruction.SetControlGroup(newTowerControlGroup); // give the tower its own unique control group
+				//warn("   updating tower control group to " + newTowerControlGroup);
+				
+				lastTowerIndex = i;// warn("   updated lastTowerIndex to " + lastTowerIndex);
+				lastTowerControlGroup = newTowerControlGroup;// warn("   updated lastTowerControlGroup to " + newTowerControlGroup);
+			}
+		}
+		else
+		{
+			//error("   (!!) failed to build wall piece, backtracking to last tower index " + lastTowerIndex);
+			
+			// wall piece foundation failed to build, backtrack and delete entities until the last tower that was succesfully built
+			var j = i - 1;
+			for (; (j >= 0 && cmd.pieces[j].template !== cmd.wallSet.tower); --j)
+			{
+				// the previous pieces should all have their entity ID registered; if not, something's wrong
+				if (!cmd.pieces[j].ent)
+				{
+					error("[TryConstructWall] Cannot backtrack to delete dangling wall pieces after placement failure; no registered entity ID");
+					continue;
+				}
+				
+				//warn("   destroying backtracked entity " + cmd.pieces[j].ent + " of template " + cmd.pieces[j].template);
+				Engine.DestroyEntity(cmd.pieces[j].ent);
+			}
+			
+			i = j + 1; // compensate for the -1 subtracted by lastBuiltPieceIndex below
+			break;
+		}
+	}
+	
+	var lastBuiltPieceIndex = i - 1;
+	var wallComplete = (lastBuiltPieceIndex == cmd.pieces.length - 1);
+	
+	//warn("lastBuiltPieceIndex = " + lastBuiltPieceIndex);
+	//warn("wall complete? " + (wallComplete ? "yes" : "no"));
+	
+	// At this point, 'i' is the index of the last wall piece that was successfully constructed (taking into account backtracking
+	// to the last tower upon foundation placement failure). Now do the second pass going right-to-left, registering the control
+	// groups of the towers to the right of each piece as their secondary control groups.
+	
+	lastTowerControlGroup = null; // control group of the last tower we've encountered, to assign to non-tower pieces
+	
+	// only start off with the ending side's snapped tower's control group if we were able to build the entire wall
+	if (cmd.endSnappedEntity && wallComplete)
+	{
+		var cmpSnappedEndObstruction = Engine.QueryInterface(cmd.endSnappedEntity, IID_Obstruction);
+		if (!cmpSnappedEndObstruction)
+		{
+			error("[TryConstructWall] Snapped entity on ending side does not have an obstruction component");
+			return;
+		}
+		
+		lastTowerControlGroup = cmpSnappedEndObstruction.GetControlGroup();
+	}
+	
+	for (var j = lastBuiltPieceIndex; j >= 0; --j)
+	{
+		var piece = cmd.pieces[j];
+		
+		//warn("---[<<]--- iteration " + j);
+		
+		// In general, lastTowerControlGroup must always be defined and valid here, except when the last successfully built
+		// piece is a tower that we didn't snap to. This can happen:
+		//    - If we didn't do any end-side snapping and the last successfully built piece is therefore a tower. Note
+		//      that in this case it doesn't matter if we did or did not complete the entire wall.
+		//    - If we did do end-side snapping, but we didn't complete the entire wall and had to end at an intermediate
+		//      tower. 
+		
+		if (lastTowerControlGroup === null || lastTowerControlGroup == INVALID_ENTITY)
+		{
+			var ok = (j == lastBuiltPieceIndex && ((!cmd.endSnappedEntity && piece.template == cmd.wallSet.tower) ||
+			                                       ( cmd.endSnappedEntity && piece.template == cmd.wallSet.tower && !wallComplete)));
+			if (!ok)
+			{
+				error("[TryConstructWall] Expected last tower control group to be available, none found (2nd pass)");
+				break;
+			}
+		}
+		
+		var cmpPieceObstruction = Engine.QueryInterface(piece.ent, IID_Obstruction);
+		// TODO: make sure piece.ent/cmpPieceObstruction exists
+		
+		if (piece.template == cmd.wallSet.tower)
+		{
+			// encountered a tower entity, update the last tower control group
+			lastTowerControlGroup = cmpPieceObstruction.GetControlGroup();
+			//warn("   updated last tower control group to " + lastTowerControlGroup);
+		}
+		else
+		{
+			// Encountered a non-tower entity, update its secondary control group.
+			// Note that the wall piece may already have its secondary control group set to the tower's entity ID from the 
+			// first pass, in which case we should validate it against lastTowerControlGroup.
+			var existingSecondaryControlGroup = cmpPieceObstruction.GetControlGroup2();
+			if (existingSecondaryControlGroup == INVALID_ENTITY)
+			{
+				cmpPieceObstruction.SetControlGroup2(lastTowerControlGroup);
+				//warn("   set wall piece secondary control group to " + lastTowerControlGroup);
+			}
+			else if (existingSecondaryControlGroup != lastTowerControlGroup)
+			{
+				error("[TryConstructWall] Existing secondary control group of non-tower entity does not match expected value (2nd pass)");
+				break;
+			}
+		}
+	}
+	
+	//warn("--- Done ---");
+	warn("Built " + (lastBuiltPieceIndex + 1) + "/" + cmd.pieces.length + " pieces");
+	//warn("------------");
+}
+
+/**
  * Remove the given list of entities from their current formations.
  */
 function RemoveFromFormation(ents)
diff --git a/binaries/data/mods/public/simulation/helpers/Walls.js b/binaries/data/mods/public/simulation/helpers/Walls.js
new file mode 100644
index 0000000..3dd66ea
--- /dev/null
+++ b/binaries/data/mods/public/simulation/helpers/Walls.js
@@ -0,0 +1,202 @@
+/**
+ * Returns the wall piece entities needed to construct a wall between start.pos and end.pos. Assumes start.pos != end.pos.
+ * The result is an array of objects, each one containing the following information about a single wall piece entity:
+ *   - 'template': the template name of the entity
+ *   - 'pos': position of the entity, as an object with keys 'x' and 'z'
+ *   - 'angle': orientation of the entity, as an angle in radians
+ * 
+ * All the pieces in the resulting array are ordered left-to-right (or right-to-left) as they appear in the physical wall.
+ * 
+ * @param placementData Object that associates the wall piece template names with information about those kinds of pieces.
+ *                        Expects placementData[templateName].templateData to contain the parsed template information about
+ *                        the template whose filename is <i>templateName</i>.
+ * @param wallSet Object that associates template names with the kinds of wall pieces that supported. Expected to contain
+ *                  template names for keys "long" (long wall segment), "medium" (medium wall segment), "short" (short wall
+ *                  segment), "tower" (intermediate tower between wall segments), "gate" (replacement for long walls).
+ * @param start Object holding the starting position of the wall. Must contain keys 'x' and 'z'.
+ * @param end   Object holding the ending position of the wall. Must contains keys 'x' and 'z'.
+ */
+function GetWallPlacement(placementData, wallSet, start, end)
+{
+	var result = [];
+	
+	var candidateSegments = [
+		{"template": wallSet.long,   "len": placementData[wallSet.long].templateData.wallPiece.length},
+		{"template": wallSet.medium, "len": placementData[wallSet.medium].templateData.wallPiece.length},
+		{"template": wallSet.short,  "len": placementData[wallSet.short].templateData.wallPiece.length},
+	];
+	
+	var towerWidth = placementData[wallSet.tower].templateData.wallPiece.length;
+	
+	var dir = {"x": end.pos.x - start.pos.x, "z": end.pos.z - start.pos.z};
+	var len = Math.sqrt(dir.x * dir.x + dir.z * dir.z);
+	
+	if (len <= towerWidth)
+		// we'll need room for at least our starting and ending towers to fit next to eachother
+		return result;
+	
+	var placement = GetWallSegmentsRec(len, candidateSegments, 0.05, 0.8, towerWidth, 0, []);
+	
+	// TODO: make sure intermediate towers are spaced out far enough for their obstructions to not overlap, implying that
+	// tower's wallpiece lengths should be > their obstruction width, which is undesirable because it prevents towers with
+	// wide bases
+	if (placement)
+	{
+		var placedEntities = placement.segments; // list of chosen candidate segments
+		var r = placement.r; // remaining distance to target without towers (must be <= (N-1) * towerWidth)
+		var s = r / (2 * placedEntities.length); // spacing
+		
+		var dirNormalized = {"x": dir.x / len, "z": dir.z / len};
+		var angle = -Math.atan2(dir.z, dir.x);    // angle of this wall segment (relative to world-space X/Z axes)
+		
+		var progress = 0;
+		for (var i = 0; i < placedEntities.length; i++)
+		{
+			var placedEntity = placedEntities[i];
+			var targetX = start.pos.x + (progress + s + placedEntity.len/2) * dirNormalized.x;
+			var targetZ = start.pos.z + (progress + s + placedEntity.len/2) * dirNormalized.z;
+			
+			result.push({
+				"template": placedEntity.template,
+				"pos": {"x": targetX, "z": targetZ},
+				"angle": angle,
+			});
+			
+			if (i < placedEntities.length - 1)
+			{
+				var towerX = start.pos.x + (progress + placedEntity.len + 2*s) * dirNormalized.x;
+				var towerZ = start.pos.z + (progress + placedEntity.len + 2*s) * dirNormalized.z;
+				
+    			result.push({
+    				"template": wallSet.tower,
+    				"pos": {"x": towerX, "z": towerZ},
+    				"angle": angle,
+    			});
+			}
+			
+			progress += placedEntity.len + 2*s;
+		}
+	}
+	else
+	{
+		error("No placement possible");
+	}
+	
+	return result;
+}
+
+/**
+ * Helper function for GetWallPlacement. Finds a list of wall segments and the corresponding remaining spacing/overlap
+ * distance "r" that will suffice to construct a wall of the given distance. It is understood that two extra towers will
+ * be placed centered at the starting and ending points of the wall. 
+ * 
+ * @param d Total distance between starting and ending points (constant throughout calls).
+ * @param candidateSegments List of candidate segments (constant throughout calls). Should be ordered longer-to-shorter
+ *                            for better execution speed.
+ * @param minOverlap Minimum overlap factor (constant throughout calls). Must have a value between 0 (meaning walls are
+ *                     not allowed to overlap towers) and 1 (meaning they're allowed to overlap towers entirely). 
+ *                     Must be <= maxOverlap.
+ * @param maxOverlap Maximum overlap factor (constant throughout calls). Must have a value between 0 (meaning walls are
+ *                     not allowed to overlap towers) and 1 (meaning they're allowed to overlap towers entirely).
+ *                     Must be >= minOverlap.
+ * @param t Length of a single tower (constant throughout calls). Acts as buffer space for wall segments (see comments).
+ * @param distSoFar Sum of all the wall segments' lengths in 'segments'.
+ * @param segments Current list of wall segments placed.
+ */
+function GetWallSegmentsRec(d, candidateSegments, minOverlap, maxOverlap, t, distSoFar, segments)
+{
+	// The idea is to find a number N of wall segments (excluding towers) so that the sum of their lengths adds up to a 
+	// value that is within certain bounds of the distance 'd' between the starting and ending points of the wall. This
+	// creates either a positive or negative 'buffer' of space, that can be compensated for by spacing the wall segments
+	// out away from each other, or inwards, overlapping each other. The spaces or overlaps can then be covered up by
+	// placing towers on top of them. In this way, the same set of wall segments can be used to span a wider range of
+	// target distances.
+	//
+	// In this function, it is understood that two extra towers will be placed centered at the starting and ending points.
+	// They are allowed to contribute to the buffer space.
+	//
+	// The buffer space equals the difference between d and the sum of the lengths of all the wall segments, and is denoted
+	// 'r' for 'remaining space'. Positive values of r mean that the walls will need to be spaced out, negative values of r 
+	// mean that they will need to overlap. Clearly, there are limits to how far wall segments can be spaced out or
+	// overlapped, depending on how much 'buffer space' each tower provides, and how far 'into' towers the wall segments are
+	// allowed to overlap.
+	//
+	// Let 't' signify the width of a tower. When there are N wall segments, then the maximum distance that can be covered
+	// using only these walls (plus the towers covering up any gaps) is achieved when the walls and towers touch outer-border-
+	// to-outer-border. Therefore, the maximum value of r is then given by:
+	// 
+	//   rMax = t/2 + (N-1)*t + t/2
+	//        = N*t
+	// 
+	// where the two half-tower widths are buffer space contributed by the implied towers on the starting and ending points.
+	// Similarly, a value rMin = -N*t can be derived for the minimal value of r. Note that a value of r = 0 means that the
+	// wall segment lengths add up to exactly d, meaning that each one starts and ends right in the center of a tower.
+	// 
+	// Thus, we establish:
+	//   -Nt <= r <= Nt
+	//
+	// We can further generalize this by adding in parameters to control the depth to within which wall segments are allowed to
+	// overlap with a tower. The bounds above assume that a wall segment is allowed to overlap across the entire range of 0
+	// (not overlapping at all, as in the upper boundary) to 1 (overlapping maximally, as in the lower boundary).
+	// 
+	// By requiring that walls overlap towers to a degree of at least 0 < minOverlap <= 1, it is clear that this lowers the
+	// distance that can be maximally reached by the same set of wall segments, compared to the value of minOverlap = 0 that
+	// we assumed to initially find Nt.
+	// 
+	// Consider a value of minOverlap = 0.5, meaning that any wall segment must protrude at least halfway into towers; in this 
+	// situation, wall segments must at least touch boundaries or overlap mutually, implying that the sum of their lengths
+	// must equal or exceed 'd', establishing an upper bound of 0 for r.
+	// Similarly, consider a value of minOverlap = 1, meaning that any wall segment must overlap towers maximally; this situation
+	// is equivalent to the one for finding the lower bound -Nt on r.
+	//
+	// With the implicit value minOverlap = 0 that yielded the upper bound Nt above, simple interpolation and a similar exercise
+	// for maxOverlap, we find:
+	//   (1-2*maxOverlap) * Nt <= r <= (1-2*minOverlap) * Nt 
+	//
+	// To find N segments that satisfy this requirement, we try placing L, M and S wall segments in turn and continue recursively
+	// as long as the value of r is not within the bounds. If continuing recursively returns an impossible configuration, we 
+	// backtrack and try a wall segment of the next length instead. Note that we should prefer to use the long segments first since
+	// they can be replaced by gates.
+	
+	for each (var candSegment in candidateSegments)
+	{
+		segments.push(candSegment);
+		
+		var newDistSoFar = distSoFar + candSegment.len;
+		var r = d - newDistSoFar;
+		
+		// TODO: these don't have to be recalculated every iteration
+		var rLowerBound = (1 - 2 * maxOverlap) * segments.length * t;
+		var rUpperBound = (1 - 2 * minOverlap) * segments.length * t;
+		
+		if (r < rLowerBound)
+		{
+			// we've allocated too much wall length, pop the last segment and try the next
+			//warn("Distance so far exceeds target, trying next level");
+			segments.pop();
+			continue;
+		}
+		else if (r > rUpperBound)
+		{
+			var recursiveResult = GetWallSegmentsRec(d, candidateSegments, minOverlap, maxOverlap, t, newDistSoFar, segments);
+			if (!recursiveResult)
+			{
+				// recursive search with this piece yielded no results, pop it and try the next one
+				segments.pop();
+				continue;
+			}
+			else
+				return recursiveResult;
+		}
+		else
+		{
+			// found a placement
+			return {"segments": segments, "r": r};
+		}
+	}
+	
+	// no placement possible :(
+	return false;
+}
+
+Engine.RegisterGlobal("GetWallPlacement", GetWallPlacement);
diff --git a/binaries/data/mods/public/simulation/templates/other/palisades_rocks_gate.xml b/binaries/data/mods/public/simulation/templates/other/palisades_rocks_gate.xml
index 5a882ef..0040043 100644
--- a/binaries/data/mods/public/simulation/templates/other/palisades_rocks_gate.xml
+++ b/binaries/data/mods/public/simulation/templates/other/palisades_rocks_gate.xml
@@ -21,4 +21,7 @@
   <VisualActor>
     <Actor>props/special/palisade_rocks_gate.xml</Actor>
   </VisualActor>
+  <WallPiece>
+    <Length>11.0</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/other/palisades_rocks_long.xml b/binaries/data/mods/public/simulation/templates/other/palisades_rocks_long.xml
index 8af8b59..fe04d11 100644
--- a/binaries/data/mods/public/simulation/templates/other/palisades_rocks_long.xml
+++ b/binaries/data/mods/public/simulation/templates/other/palisades_rocks_long.xml
@@ -22,4 +22,7 @@
   <VisualActor>
     <Actor>props/special/palisade_rocks_long.xml</Actor>
   </VisualActor>
+  <WallPiece>
+    <Length>11.0</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/other/palisades_rocks_medium.xml b/binaries/data/mods/public/simulation/templates/other/palisades_rocks_medium.xml
index db4a799..dca7de4 100644
--- a/binaries/data/mods/public/simulation/templates/other/palisades_rocks_medium.xml
+++ b/binaries/data/mods/public/simulation/templates/other/palisades_rocks_medium.xml
@@ -22,4 +22,7 @@
   <VisualActor>
     <Actor>props/special/palisade_rocks_medium.xml</Actor>
   </VisualActor>
+  <WallPiece>
+    <Length>8.0</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/other/palisades_rocks_short.xml b/binaries/data/mods/public/simulation/templates/other/palisades_rocks_short.xml
index f7a6bcf..bfd3bb3 100644
--- a/binaries/data/mods/public/simulation/templates/other/palisades_rocks_short.xml
+++ b/binaries/data/mods/public/simulation/templates/other/palisades_rocks_short.xml
@@ -22,5 +22,7 @@
   <VisualActor>
     <Actor>props/special/palisade_rocks_short.xml</Actor>
   </VisualActor>
+  <WallPiece>
+    <Length>4.0</Length>
+  </WallPiece>
 </Entity>
-
diff --git a/binaries/data/mods/public/simulation/templates/other/palisades_rocks_tower.xml b/binaries/data/mods/public/simulation/templates/other/palisades_rocks_tower.xml
index 8a56c70..f2b70fa 100644
--- a/binaries/data/mods/public/simulation/templates/other/palisades_rocks_tower.xml
+++ b/binaries/data/mods/public/simulation/templates/other/palisades_rocks_tower.xml
@@ -22,4 +22,7 @@
   <VisualActor>
     <Actor>props/special/palisade_rocks_tower.xml</Actor>
   </VisualActor>
+  <WallPiece>
+    <Length>4.0</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/celt_wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/celt_wall_gate.xml
index 0675ccc..63fa2e6 100644
--- a/binaries/data/mods/public/simulation/templates/structures/celt_wall_gate.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/celt_wall_gate.xml
@@ -15,4 +15,7 @@
   <VisualActor>
     <Actor>structures/celts/wall_gate.xml</Actor>
   </VisualActor>
+  <WallPiece>
+    <Length>22.0</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/celt_wall_long.xml b/binaries/data/mods/public/simulation/templates/structures/celt_wall_long.xml
index 41d1fdc..a9a4451 100644
--- a/binaries/data/mods/public/simulation/templates/structures/celt_wall_long.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/celt_wall_long.xml
@@ -15,4 +15,7 @@
   <VisualActor>
     <Actor>structures/celts/wall_long.xml</Actor>
   </VisualActor>
+  <WallPiece>
+    <Length>22.0</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/celt_wall_medium.xml b/binaries/data/mods/public/simulation/templates/structures/celt_wall_medium.xml
index 4b51497..851af02 100644
--- a/binaries/data/mods/public/simulation/templates/structures/celt_wall_medium.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/celt_wall_medium.xml
@@ -21,4 +21,7 @@
   <VisualActor>
     <Actor>structures/celts/wall_medium.xml</Actor>
   </VisualActor>
+  <WallPiece>
+    <Length>22.0</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/celt_wall_short.xml b/binaries/data/mods/public/simulation/templates/structures/celt_wall_short.xml
index 1f17003..66523a9 100644
--- a/binaries/data/mods/public/simulation/templates/structures/celt_wall_short.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/celt_wall_short.xml
@@ -21,4 +21,7 @@
   <VisualActor>
     <Actor>structures/celts/wall_short.xml</Actor>
   </VisualActor>
+  <WallPiece>
+    <Length>22.0</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/celt_wall_tower.xml b/binaries/data/mods/public/simulation/templates/structures/celt_wall_tower.xml
index d67c982..8ebcd99 100644
--- a/binaries/data/mods/public/simulation/templates/structures/celt_wall_tower.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/celt_wall_tower.xml
@@ -15,4 +15,7 @@
   <VisualActor>
     <Actor>structures/celts/wall_tower.xml</Actor>
   </VisualActor>
+  <WallPiece>
+    <Length>6.0</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/hele_wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/hele_wall_gate.xml
index 5726f89..1c1ed7b 100644
--- a/binaries/data/mods/public/simulation/templates/structures/hele_wall_gate.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/hele_wall_gate.xml
@@ -21,4 +21,7 @@
   <VisualActor>
     <Actor>structures/hellenes/wall_gate.xml</Actor>
   </VisualActor>
+  <WallPiece>
+    <Length>35.0</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/hele_wall_long.xml b/binaries/data/mods/public/simulation/templates/structures/hele_wall_long.xml
index 280419e..a254cd2 100644
--- a/binaries/data/mods/public/simulation/templates/structures/hele_wall_long.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/hele_wall_long.xml
@@ -21,4 +21,7 @@
   <VisualActor>
     <Actor>structures/hellenes/wall_long.xml</Actor>
   </VisualActor>
+  <WallPiece>
+    <Length>34.0</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/hele_wall_med.xml b/binaries/data/mods/public/simulation/templates/structures/hele_wall_med.xml
index 13550bc..cfd2034 100644
--- a/binaries/data/mods/public/simulation/templates/structures/hele_wall_med.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/hele_wall_med.xml
@@ -21,4 +21,7 @@
   <VisualActor>
     <Actor>structures/hellenes/wall_medium.xml</Actor>
   </VisualActor>
+  <WallPiece>
+    <Length>21.0</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/hele_wall_medium.xml b/binaries/data/mods/public/simulation/templates/structures/hele_wall_medium.xml
index 2db9bd1..1f227a8 100644
--- a/binaries/data/mods/public/simulation/templates/structures/hele_wall_medium.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/hele_wall_medium.xml
@@ -21,4 +21,7 @@
   <VisualActor>
     <Actor>structures/hellenes/wall_medium.xml</Actor>
   </VisualActor>
+  <WallPiece>
+    <Length>21.0</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/hele_wall_short.xml b/binaries/data/mods/public/simulation/templates/structures/hele_wall_short.xml
index e3be746..c7ecf13 100644
--- a/binaries/data/mods/public/simulation/templates/structures/hele_wall_short.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/hele_wall_short.xml
@@ -21,4 +21,7 @@
   <VisualActor>
     <Actor>structures/hellenes/wall_short.xml</Actor>
   </VisualActor>
+  <WallPiece>
+    <Length>10.0</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/hele_wall_tower.xml b/binaries/data/mods/public/simulation/templates/structures/hele_wall_tower.xml
index c5e81aa..0a19abf 100644
--- a/binaries/data/mods/public/simulation/templates/structures/hele_wall_tower.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/hele_wall_tower.xml
@@ -21,4 +21,7 @@
   <VisualActor>
     <Actor>structures/hellenes/wall_tower.xml</Actor>
   </VisualActor>
+  <WallPiece>
+    <Length>5.0</Length>
+  </WallPiece>
 </Entity>
\ No newline at end of file
diff --git a/binaries/data/mods/public/simulation/templates/structures/iber_wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/iber_wall_gate.xml
index 1030027..d005300 100644
--- a/binaries/data/mods/public/simulation/templates/structures/iber_wall_gate.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/iber_wall_gate.xml
@@ -15,4 +15,7 @@
   <VisualActor>
     <Actor>structures/iberians/wall_gate.xml</Actor>
   </VisualActor>
+  <WallPiece>
+  	<Length>32.0</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/iber_wall_long.xml b/binaries/data/mods/public/simulation/templates/structures/iber_wall_long.xml
index 8fd77e4..e339f42 100644
--- a/binaries/data/mods/public/simulation/templates/structures/iber_wall_long.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/iber_wall_long.xml
@@ -15,4 +15,7 @@
   <VisualActor>
     <Actor>structures/iberians/wall_long.xml</Actor>
   </VisualActor>
+  <WallPiece>
+  	<Length>34.0</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/iber_wall_medium.xml b/binaries/data/mods/public/simulation/templates/structures/iber_wall_medium.xml
index 9bbdf5d..3435685 100644
--- a/binaries/data/mods/public/simulation/templates/structures/iber_wall_medium.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/iber_wall_medium.xml
@@ -21,4 +21,7 @@
   <VisualActor>
     <Actor>structures/iberians/wall_medium.xml</Actor>
   </VisualActor>
+  <WallPiece>
+  	<Length>23.0</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/iber_wall_short.xml b/binaries/data/mods/public/simulation/templates/structures/iber_wall_short.xml
index fa32ee1..2436a59 100644
--- a/binaries/data/mods/public/simulation/templates/structures/iber_wall_short.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/iber_wall_short.xml
@@ -21,4 +21,7 @@
   <VisualActor>
     <Actor>structures/iberians/wall_short.xml</Actor>
   </VisualActor>
+  <WallPiece>
+  	<Length>10.0</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/iber_wall_tower.xml b/binaries/data/mods/public/simulation/templates/structures/iber_wall_tower.xml
index 264de9e..8f368de 100644
--- a/binaries/data/mods/public/simulation/templates/structures/iber_wall_tower.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/iber_wall_tower.xml
@@ -15,4 +15,7 @@
   <VisualActor>
     <Actor>structures/iberians/wall_tower.xml</Actor>
   </VisualActor>
+  <WallPiece>
+  	<Length>8.5</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/pers_wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/pers_wall_gate.xml
index 7218abe..d423d91 100644
--- a/binaries/data/mods/public/simulation/templates/structures/pers_wall_gate.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/pers_wall_gate.xml
@@ -21,4 +21,7 @@
   <VisualActor>
     <Actor>structures/persians/wall_gate.xml</Actor>
   </VisualActor>
+  <WallPiece>
+  	<Length>34.0</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/pers_wall_long.xml b/binaries/data/mods/public/simulation/templates/structures/pers_wall_long.xml
index 48cad21..c2e7dd1 100644
--- a/binaries/data/mods/public/simulation/templates/structures/pers_wall_long.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/pers_wall_long.xml
@@ -21,4 +21,7 @@
   <VisualActor>
     <Actor>structures/persians/wall_long.xml</Actor>
   </VisualActor>
+  <WallPiece>
+  	<Length>34.0</Length>
+  </WallPiece>
 </Entity>
\ No newline at end of file
diff --git a/binaries/data/mods/public/simulation/templates/structures/pers_wall_medium.xml b/binaries/data/mods/public/simulation/templates/structures/pers_wall_medium.xml
index e983e18..8a9ef69 100644
--- a/binaries/data/mods/public/simulation/templates/structures/pers_wall_medium.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/pers_wall_medium.xml
@@ -21,4 +21,7 @@
   <VisualActor>
     <Actor>structures/persians/wall_medium.xml</Actor>
   </VisualActor>
+  <WallPiece>
+  	<Length>21.0</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/pers_wall_short.xml b/binaries/data/mods/public/simulation/templates/structures/pers_wall_short.xml
index d92c6b6..c5a096c 100644
--- a/binaries/data/mods/public/simulation/templates/structures/pers_wall_short.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/pers_wall_short.xml
@@ -21,4 +21,7 @@
   <VisualActor>
     <Actor>structures/persians/wall_short.xml</Actor>
   </VisualActor>
+  <WallPiece>
+  	<Length>10.0</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/pers_wall_tower.xml b/binaries/data/mods/public/simulation/templates/structures/pers_wall_tower.xml
index 75cadce..ec80dcd 100644
--- a/binaries/data/mods/public/simulation/templates/structures/pers_wall_tower.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/pers_wall_tower.xml
@@ -21,4 +21,7 @@
   <VisualActor>
     <Actor>structures/persians/wall_tower.xml</Actor>
   </VisualActor>
+  <WallPiece>
+  	<Length>5.5</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/rome_wall_gate.xml
index ad4b54e..20fc90a 100644
--- a/binaries/data/mods/public/simulation/templates/structures/rome_wall_gate.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/rome_wall_gate.xml
@@ -15,4 +15,7 @@
   <VisualActor>
     <Actor>structures/romans/wall_gate.xml</Actor>
   </VisualActor>
+  <WallPiece>
+    <Length>34.0</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_wall_long.xml b/binaries/data/mods/public/simulation/templates/structures/rome_wall_long.xml
index ae4a5ef..b0003b8 100644
--- a/binaries/data/mods/public/simulation/templates/structures/rome_wall_long.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/rome_wall_long.xml
@@ -15,4 +15,7 @@
   <VisualActor>
     <Actor>structures/romans/wall_long.xml</Actor>
   </VisualActor>
+  <WallPiece>
+    <Length>34.0</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_wall_medium.xml b/binaries/data/mods/public/simulation/templates/structures/rome_wall_medium.xml
index 169ccbe..bc67e85 100644
--- a/binaries/data/mods/public/simulation/templates/structures/rome_wall_medium.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/rome_wall_medium.xml
@@ -21,4 +21,7 @@
   <VisualActor>
     <Actor>structures/romans/wall_medium.xml</Actor>
   </VisualActor>
+  <WallPiece>
+    <Length>22.0</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_wall_short.xml b/binaries/data/mods/public/simulation/templates/structures/rome_wall_short.xml
index 8a79992..58ab55b 100644
--- a/binaries/data/mods/public/simulation/templates/structures/rome_wall_short.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/rome_wall_short.xml
@@ -21,4 +21,7 @@
   <VisualActor>
     <Actor>structures/romans/wall_short.xml</Actor>
   </VisualActor>
+  <WallPiece>
+    <Length>10.0</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_wall_tower.xml b/binaries/data/mods/public/simulation/templates/structures/rome_wall_tower.xml
index 6f68a9c..252eec4 100644
--- a/binaries/data/mods/public/simulation/templates/structures/rome_wall_tower.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/rome_wall_tower.xml
@@ -15,4 +15,7 @@
   <VisualActor>
     <Actor>structures/romans/wall_tower.xml</Actor>
   </VisualActor>
+  <WallPiece>
+    <Length>6.5</Length>
+  </WallPiece>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/wallsets/celt_palisade.xml b/binaries/data/mods/public/simulation/templates/structures/wallsets/celt_palisade.xml
new file mode 100644
index 0000000..ff082ce
--- /dev/null
+++ b/binaries/data/mods/public/simulation/templates/structures/wallsets/celt_palisade.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Abstract entity to serve as a bare-minimum dummy constructable entity to initiate wall placement.
+Defines the set of actual entities that are part of the same wall construction system (i.e., towers, 
+gates, wall segments of various length, etc.) -->
+<Entity>
+  <Identity>
+    <Icon>gaia/special_palisade.png</Icon>
+    <Civ>celt</Civ>
+    <SpecificName>WALLSET</SpecificName>
+    <History>WALLSET</History>
+    <!-- TODO: Undesirable copy/pasta from the wall templates :( -->
+    <Classes datatype="tokens">Town Wall</Classes>
+    <GenericName>Palisade Wallset</GenericName>
+    <Tooltip>WALLSET</Tooltip>
+  </Identity>
+  <WallSet>
+  	<Tower>other/palisades_rocks_tower</Tower>
+  	<Gate>other/palisades_rocks_gate</Gate>
+  	<WallLong>other/palisades_rocks_long</WallLong>
+  	<WallMedium>other/palisades_rocks_medium</WallMedium>
+  	<WallShort>other/palisades_rocks_short</WallShort>
+  </WallSet>
+</Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/wallsets/celt_stone.xml b/binaries/data/mods/public/simulation/templates/structures/wallsets/celt_stone.xml
new file mode 100644
index 0000000..c3baf09
--- /dev/null
+++ b/binaries/data/mods/public/simulation/templates/structures/wallsets/celt_stone.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Abstract entity to serve as a bare-minimum dummy constructable entity to initiate wall placement.
+Defines the set of actual entities that are part of the same wall construction system (i.e., towers, 
+gates, wall segments of various length, etc.) -->
+<Entity>
+  <Identity>
+    <Icon>structures/wall.png</Icon>
+    <Civ>iber</Civ>
+    <SpecificName>Celt Wallset</SpecificName>
+    <!-- TODO: Undesirable copy/pasta from the wall templates :( -->
+    <Classes datatype="tokens">Town Wall</Classes>
+    <GenericName>City Wall</GenericName>
+    <Tooltip>(Celt WallSet)</Tooltip>
+  </Identity>
+  <WallSet>
+  	<Tower>structures/celt_wall_tower</Tower>
+  	<Gate>structures/celt_wall_gate</Gate>
+  	<WallLong>structures/celt_wall_long</WallLong>
+  	<WallMedium>structures/celt_wall_medium</WallMedium>
+  	<WallShort>structures/celt_wall_short</WallShort>
+  </WallSet>
+</Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/wallsets/hele_stone.xml b/binaries/data/mods/public/simulation/templates/structures/wallsets/hele_stone.xml
new file mode 100644
index 0000000..71a6e26
--- /dev/null
+++ b/binaries/data/mods/public/simulation/templates/structures/wallsets/hele_stone.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Abstract entity to serve as a bare-minimum dummy constructable entity to initiate wall placement.
+Defines the set of actual entities that are part of the same wall construction system (i.e., towers, 
+gates, wall segments of various length, etc.) -->
+<Entity>
+  <Identity>
+    <Icon>structures/wall.png</Icon>
+    <Civ>hele</Civ>
+    <SpecificName>HELE WALLSET</SpecificName>
+    <History>All Hellenic cities were surrounded by stone walls for protection against enemy raids. Some of these fortifications, like the Athenian Long Walls, for example, were massive structures.</History>
+    <!-- TODO: Undesirable copy/pasta from the wall templates :( -->
+    <Classes datatype="tokens">Town Wall</Classes>
+    <GenericName>City Wall</GenericName>
+    <Tooltip>HELE WALLSET</Tooltip>
+  </Identity>
+  <WallSet>
+  	<Tower>structures/hele_wall_tower</Tower>
+  	<Gate>structures/hele_wall_gate</Gate>
+  	<WallLong>structures/hele_wall_long</WallLong>
+  	<WallMedium>structures/hele_wall_med</WallMedium>
+  	<WallShort>structures/hele_wall_short</WallShort>
+  </WallSet>
+</Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/wallsets/iber_stone.xml b/binaries/data/mods/public/simulation/templates/structures/wallsets/iber_stone.xml
new file mode 100644
index 0000000..669298e
--- /dev/null
+++ b/binaries/data/mods/public/simulation/templates/structures/wallsets/iber_stone.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Abstract entity to serve as a bare-minimum dummy constructable entity to initiate wall placement.
+Defines the set of actual entities that are part of the same wall construction system (i.e., towers, 
+gates, wall segments of various length, etc.) -->
+<Entity>
+  <Identity>
+    <Icon>structures/wall.png</Icon>
+    <Civ>iber</Civ>
+    <SpecificName>Iber Wallset</SpecificName>
+    <!-- TODO: Undesirable copy/pasta from the wall templates :( -->
+    <Classes datatype="tokens">Town Wall</Classes>
+    <GenericName>City Wall</GenericName>
+    <Tooltip>(Iber WallSet)</Tooltip>
+  </Identity>
+  <WallSet>
+  	<Tower>structures/iber_wall_tower</Tower>
+  	<Gate>structures/iber_wall_gate</Gate>
+  	<WallLong>structures/iber_wall_long</WallLong>
+  	<WallMedium>structures/iber_wall_medium</WallMedium>
+  	<WallShort>structures/iber_wall_short</WallShort>
+  </WallSet>
+</Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/wallsets/pers_stone.xml b/binaries/data/mods/public/simulation/templates/structures/wallsets/pers_stone.xml
new file mode 100644
index 0000000..4ec9533
--- /dev/null
+++ b/binaries/data/mods/public/simulation/templates/structures/wallsets/pers_stone.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Abstract entity to serve as a bare-minimum dummy constructable entity to initiate wall placement.
+Defines the set of actual entities that are part of the same wall construction system (i.e., towers, 
+gates, wall segments of various length, etc.) -->
+<Entity>
+  <Identity>
+    <Icon>structures/wall.png</Icon>
+    <Civ>iber</Civ>
+    <SpecificName>Persian WallSet</SpecificName>
+    <!-- TODO: Undesirable copy/pasta from the wall templates :( -->
+    <Classes datatype="tokens">Town Wall</Classes>
+    <GenericName>City Wall</GenericName>
+    <Tooltip>Persian WallSet</Tooltip>
+  </Identity>
+  <WallSet>
+  	<Tower>structures/pers_wall_tower</Tower>
+  	<Gate>structures/pers_wall_gate</Gate>
+  	<WallLong>structures/pers_wall_long</WallLong>
+  	<WallMedium>structures/pers_wall_medium</WallMedium>
+  	<WallShort>structures/pers_wall_short</WallShort>
+  </WallSet>
+</Entity>
diff --git a/binaries/data/mods/public/simulation/templates/structures/wallsets/rome_stone.xml b/binaries/data/mods/public/simulation/templates/structures/wallsets/rome_stone.xml
new file mode 100644
index 0000000..d5ebab1
--- /dev/null
+++ b/binaries/data/mods/public/simulation/templates/structures/wallsets/rome_stone.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Abstract entity to serve as a bare-minimum dummy constructable entity to initiate wall placement.
+Defines the set of actual entities that are part of the same wall construction system (i.e., towers, 
+gates, wall segments of various length, etc.) -->
+<Entity>
+  <Identity>
+    <Icon>structures/wall.png</Icon>
+    <Civ>iber</Civ>
+    <SpecificName>Roman WallSet</SpecificName>
+    <!-- TODO: Undesirable copy/pasta from the wall templates :( -->
+    <Classes datatype="tokens">Town Wall</Classes>
+    <GenericName>City Wall</GenericName>
+    <Tooltip>Roman WallSet</Tooltip>
+  </Identity>
+  <WallSet>
+  	<Tower>structures/rome_wall_tower</Tower>
+  	<Gate>structures/rome_wall_gate</Gate>
+  	<WallLong>structures/rome_wall_long</WallLong>
+  	<WallMedium>structures/rome_wall_medium</WallMedium>
+  	<WallShort>structures/rome_wall_short</WallShort>
+  </WallSet>
+</Entity>
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml b/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml
index 4d51082..3ef5b3a 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml
@@ -20,9 +20,7 @@
       structures/{civ}_dock
       structures/{civ}_outpost
       structures/{civ}_defense_tower
-      structures/{civ}_wall
-      structures/{civ}_wall_tower
-      structures/{civ}_wall_gate
+      <!-- TODO: add stone wallset here? do all civs have stone wallsets? -->
       structures/{civ}_fortress
     </Entities>
   </Builder>
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_support_slave.xml b/binaries/data/mods/public/simulation/templates/template_unit_support_slave.xml
index c90ad5f..8db4942 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_support_slave.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_support_slave.xml
@@ -19,9 +19,7 @@
       structures/{civ}_barracks
       structures/{civ}_dock
       structures/{civ}_scout_tower
-      structures/{civ}_wall
-      structures/{civ}_wall_tower
-      structures/{civ}_wall_gate
+      special/wallsets/{civ}_land
       structures/{civ}_fortress
     </Entities>
   </Builder>
diff --git a/binaries/data/mods/public/simulation/templates/units/celt_fanatic.xml b/binaries/data/mods/public/simulation/templates/units/celt_fanatic.xml
index 096de32..ffd1fca 100644
--- a/binaries/data/mods/public/simulation/templates/units/celt_fanatic.xml
+++ b/binaries/data/mods/public/simulation/templates/units/celt_fanatic.xml
@@ -12,6 +12,17 @@
       <Hack>60.0</Hack>
     </Charge>
   </Attack>
+  <!-- TODO: remove me; temporarily here for ease of playing with the wall system -->
+  <Builder>
+  	<Rate>50.0</Rate>
+  	<Entities datatype="tokens">
+      structures/wallsets/{civ}_palisade
+      structures/wallsets/iber_stone
+      structures/wallsets/pers_stone
+      structures/wallsets/hele_stone
+      structures/wallsets/rome_stone
+    </Entities>
+  </Builder>
   <Cost>
     <Resources>
       <food>0</food>
diff --git a/binaries/system/ActorEditor.exe b/binaries/system/ActorEditor.exe
deleted file mode 100644
index 15cdfc5..0000000
Binary files a/binaries/system/ActorEditor.exe and /dev/null differ
diff --git a/source/graphics/Overlay.h b/source/graphics/Overlay.h
index ce6f7c4..6d0dfe7 100644
--- a/source/graphics/Overlay.h
+++ b/source/graphics/Overlay.h
@@ -21,6 +21,7 @@
 #include "graphics/RenderableObject.h"
 #include "graphics/Texture.h"
 #include "maths/Vector3D.h"
+#include "maths/FixedVector3D.h"
 #include "ps/Overlay.h" // CColor  (TODO: that file has nothing to do with overlays, it should be renamed)
 
 class CTerrain;
@@ -37,10 +38,9 @@ struct SOverlayLine
 	std::vector<float> m_Coords; // (x, y, z) vertex coordinate triples; shape is not automatically closed
 	u8 m_Thickness; // pixels
 
-	/// Utility function; pushes three vertex coordinates at once onto the coordinates array
 	void PushCoords(const float x, const float y, const float z) { m_Coords.push_back(x); m_Coords.push_back(y); m_Coords.push_back(z); }
-	/// Utility function; pushes a vertex location onto the coordinates array
 	void PushCoords(const CVector3D& v) { PushCoords(v.X, v.Y, v.Z); }
+	void PushCoords(const CFixedVector3D& v) { PushCoords(v.X.ToFloat(), v.Y.ToFloat(), v.Z.ToFloat()); }
 };
 
 /**
diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp
index 4fad858..a097b17 100644
--- a/source/gui/scripting/ScriptFunctions.cpp
+++ b/source/gui/scripting/ScriptFunctions.cpp
@@ -141,9 +141,9 @@ std::vector<entity_id_t> PickFriendlyEntitiesOnScreen(void* cbdata, int player)
 	return PickFriendlyEntitiesInRect(cbdata, 0, 0, g_xres, g_yres, player);
 }
 
-std::vector<entity_id_t> PickSimilarFriendlyEntities(void* UNUSED(cbdata), std::string templateName, bool includeOffScreen, bool matchRank)
+std::vector<entity_id_t> PickSimilarFriendlyEntities(void* UNUSED(cbdata), std::string templateName, bool includeOffScreen, bool matchRank, bool allowFoundations)
 {
-	return EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, g_Game->GetPlayerID(), includeOffScreen, matchRank, false);
+	return EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, g_Game->GetPlayerID(), includeOffScreen, matchRank, false, allowFoundations);
 }
 
 CFixedVector3D GetTerrainAtPoint(void* UNUSED(cbdata), int x, int y)
@@ -574,7 +574,7 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
 	scriptInterface.RegisterFunction<std::vector<entity_id_t>, int, int, &PickEntitiesAtPoint>("PickEntitiesAtPoint");
 	scriptInterface.RegisterFunction<std::vector<entity_id_t>, int, int, int, int, int, &PickFriendlyEntitiesInRect>("PickFriendlyEntitiesInRect");
 	scriptInterface.RegisterFunction<std::vector<entity_id_t>, int, &PickFriendlyEntitiesOnScreen>("PickFriendlyEntitiesOnScreen");
-	scriptInterface.RegisterFunction<std::vector<entity_id_t>, std::string, bool, bool, &PickSimilarFriendlyEntities>("PickSimilarFriendlyEntities");
+	scriptInterface.RegisterFunction<std::vector<entity_id_t>, std::string, bool, bool, bool, &PickSimilarFriendlyEntities>("PickSimilarFriendlyEntities");
 	scriptInterface.RegisterFunction<CFixedVector3D, int, int, &GetTerrainAtPoint>("GetTerrainAtPoint");
 
 	// Network / game setup functions
diff --git a/source/lib/self_test.h b/source/lib/self_test.h
index 889616a..27e3870 100644
--- a/source/lib/self_test.h
+++ b/source/lib/self_test.h
@@ -279,6 +279,7 @@ std::vector<T> ts_make_vector(T* start, size_t size_bytes)
 	return std::vector<T>(start, start+(size_bytes/sizeof(T)));
 }
 #define TS_ASSERT_VECTOR_EQUALS_ARRAY(vec1, array) TS_ASSERT_EQUALS(vec1, ts_make_vector((array), sizeof(array)))
+#define TS_ASSERT_VECTOR_CONTAINS(vec1, element) TS_ASSERT(std::find((vec1).begin(), (vec1).end(), element) != (vec1).end());
 
 class ScriptInterface;
 // Script-based testing setup (defined in test_setup.cpp). Defines TS_* functions.
diff --git a/source/simulation2/components/CCmpObstruction.cpp b/source/simulation2/components/CCmpObstruction.cpp
index 92325ba..cd23aab 100644
--- a/source/simulation2/components/CCmpObstruction.cpp
+++ b/source/simulation2/components/CCmpObstruction.cpp
@@ -20,11 +20,11 @@
 #include "simulation2/system/Component.h"
 #include "ICmpObstruction.h"
 
+#include "ps/CLogger.h"
+#include "simulation2/MessageTypes.h"
 #include "simulation2/components/ICmpObstructionManager.h"
 #include "simulation2/components/ICmpPosition.h"
 
-#include "simulation2/MessageTypes.h"
-
 /**
  * Obstruction implementation. This keeps the ICmpPathfinder's model of the world updated when the
  * entities move and die, with shapes derived from ICmpFootprint.
@@ -49,16 +49,31 @@ public:
 		STATIC,
 		UNIT
 	} m_Type;
+
 	entity_pos_t m_Size0; // radius or width
 	entity_pos_t m_Size1; // radius or depth
 	flags_t m_TemplateFlags;
 
 	// Dynamic state:
 
-	bool m_Active; // whether the obstruction is obstructing or just an inactive placeholder
+	/// Whether the obstruction is actively obstructing or just an inactive placeholder
+	bool m_Active; 
 	bool m_Moving;
+
+	/**
+	 * Unique identifier for grouping obstruction shapes, typically to have member shapes ignore 
+	 * each other during obstruction tests. Defaults to the entity ID.
+	 * 
+	 * TODO: if needed, perhaps add a mask to specify with respect to which flags members of the
+	 * group should ignore each other.
+	 */
 	entity_id_t m_ControlGroup;
+	entity_id_t m_ControlGroup2;
+
+	/// Identifier of this entity's obstruction shape. Contains structure, but should be treated
+	/// as opaque here.
 	tag_t m_Tag;
+	/// Set of flags affecting the behaviour of this entity's obstruction shape.
 	flags_t m_Flags;
 
 	static std::string GetSchema()
@@ -139,6 +154,7 @@ public:
 		m_Tag = tag_t();
 		m_Moving = false;
 		m_ControlGroup = GetEntityId();
+		m_ControlGroup2 = INVALID_ENTITY;
 	}
 
 	virtual void Deinit()
@@ -194,7 +210,7 @@ public:
 				// Need to create a new pathfinder shape:
 				if (m_Type == STATIC)
 					m_Tag = cmpObstructionManager->AddStaticShape(GetEntityId(),
-						data.x, data.z, data.a, m_Size0, m_Size1, m_Flags);
+						data.x, data.z, data.a, m_Size0, m_Size1, m_Flags, m_ControlGroup, m_ControlGroup2);
 				else
 					m_Tag = cmpObstructionManager->AddUnitShape(GetEntityId(),
 						data.x, data.z, m_Size0, (flags_t)(m_Flags | (m_Moving ? ICmpObstructionManager::FLAG_MOVING : 0)), m_ControlGroup);
@@ -241,10 +257,11 @@ public:
 			if (!cmpPosition->IsInWorld())
 				return; // don't need an obstruction
 
+			// TODO: code duplication from message handlers
 			CFixedVector2D pos = cmpPosition->GetPosition2D();
 			if (m_Type == STATIC)
 				m_Tag = cmpObstructionManager->AddStaticShape(GetEntityId(),
-					pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, m_Flags);
+					pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, m_Flags, m_ControlGroup, m_ControlGroup2);
 			else
 				m_Tag = cmpObstructionManager->AddUnitShape(GetEntityId(),
 					pos.X, pos.Y, m_Size0, (flags_t)(m_Flags | (m_Moving ? ICmpObstructionManager::FLAG_MOVING : 0)), m_ControlGroup);
@@ -255,6 +272,7 @@ public:
 
 			// Delete the obstruction shape
 
+			// TODO: code duplication from message handlers
 			if (m_Tag.valid())
 			{
 				CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
@@ -346,11 +364,21 @@ public:
 		if (!cmpPathfinder)
 			return false; // error
 
+		// required precondition to use SkipControlGroupsRequireFlagObstructionFilter
+		if (m_ControlGroup == INVALID_ENTITY)
+		{
+			LOGERROR(L"[CmpObstruction] Cannot test for foundation obstructions; primary control group must be valid");
+			return false;
+		}
+
 		// Get passability class
 		ICmpPathfinder::pass_class_t passClass = cmpPathfinder->GetPassabilityClass(className);
 
-		// Ignore collisions with self, or with non-foundation-blocking obstructions
-		SkipTagFlagsObstructionFilter filter(m_Tag, ICmpObstructionManager::FLAG_BLOCK_FOUNDATION);
+		// Ignore collisions within the same control group, or with other non-foundation-blocking shapes.
+		// Note that, since the control group for each entity defaults to the entity's ID, this is typically 
+		// equivalent to only ignoring the entity's own shape and other non-foundation-blocking shapes.
+		SkipControlGroupsRequireFlagObstructionFilter filter(m_ControlGroup, m_ControlGroup2,
+			ICmpObstructionManager::FLAG_BLOCK_FOUNDATION);
 
 		if (m_Type == STATIC)
 			return cmpPathfinder->CheckBuildingPlacement(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, GetEntityId(), passClass);
@@ -375,8 +403,18 @@ public:
 		if (!cmpObstructionManager)
 			return ret; // error
 
-		// Ignore collisions with self, or with non-construction-blocking obstructions
-		SkipTagFlagsObstructionFilter filter(m_Tag, ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION);
+		// required precondition to use SkipControlGroupsRequireFlagObstructionFilter
+		if (m_ControlGroup == INVALID_ENTITY)
+		{
+			LOGERROR(L"[CmpObstruction] Cannot test for construction obstructions; primary control group must be valid");
+			return ret;
+		}
+
+		// Ignore collisions within the same control group, or with other non-construction-blocking shapes.
+		// Note that, since the control group for each entity defaults to the entity's ID, this is typically 
+		// equivalent to only ignoring the entity's own shape and other non-construction-blocking shapes. 
+		SkipControlGroupsRequireFlagObstructionFilter filter(m_ControlGroup, m_ControlGroup2,
+			ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION);
 
 		if (m_Type == STATIC)
 			cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, &ret);
@@ -401,12 +439,41 @@ public:
 	virtual void SetControlGroup(entity_id_t group)
 	{
 		m_ControlGroup = group;
+		UpdateControlGroups();
+	}
 
-		if (m_Tag.valid() && m_Type == UNIT)
+	virtual void SetControlGroup2(entity_id_t group2)
+	{
+		m_ControlGroup2 = group2;
+		UpdateControlGroups();
+	}
+
+	virtual entity_id_t GetControlGroup() 
+	{
+		return m_ControlGroup;
+	}
+
+	virtual entity_id_t GetControlGroup2() 
+	{
+		return m_ControlGroup2;
+	}
+
+	void UpdateControlGroups()
+	{
+		if (m_Tag.valid())
 		{
 			CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
 			if (cmpObstructionManager)
-				cmpObstructionManager->SetUnitControlGroup(m_Tag, m_ControlGroup);
+			{
+				if (m_Type == UNIT)
+				{
+					cmpObstructionManager->SetUnitControlGroup(m_Tag, m_ControlGroup);
+				}
+				else if (m_Type == STATIC)
+				{
+					cmpObstructionManager->SetStaticControlGroup(m_Tag, m_ControlGroup, m_ControlGroup2);
+				}
+			}
 		}
 	}
 
diff --git a/source/simulation2/components/CCmpObstructionManager.cpp b/source/simulation2/components/CCmpObstructionManager.cpp
index e1a8d6a..4836eab 100644
--- a/source/simulation2/components/CCmpObstructionManager.cpp
+++ b/source/simulation2/components/CCmpObstructionManager.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2011 Wildfire Games.
+/* Copyright (C) 2012 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,7 @@
 #include "ps/Overlay.h"
 #include "ps/Profile.h"
 #include "renderer/Scene.h"
+#include "ps/CLogger.h"
 
 // Externally, tags are opaque non-zero positive integers.
 // Internally, they are tagged (by shape) indexes into shape lists.
@@ -65,6 +66,8 @@ struct StaticShape
 	CFixedVector2D u, v; // orthogonal unit vectors - axes of local coordinate space
 	entity_pos_t hw, hh; // half width/height in local coordinate space
 	ICmpObstructionManager::flags_t flags;
+	entity_id_t group;
+	entity_id_t group2;
 };
 
 /**
@@ -102,6 +105,7 @@ struct SerializeStaticShape
 		serialize.NumberFixed_Unbounded("hw", value.hw);
 		serialize.NumberFixed_Unbounded("hh", value.hh);
 		serialize.NumberU8_Unbounded("flags", value.flags);
+		serialize.NumberU32_Unbounded("group", value.group);
 	}
 };
 
@@ -257,14 +261,14 @@ public:
 		return UNIT_INDEX_TO_TAG(id);
 	}
 
-	virtual tag_t AddStaticShape(entity_id_t ent, entity_pos_t x, entity_pos_t z, entity_angle_t a, entity_pos_t w, entity_pos_t h, flags_t flags)
+	virtual tag_t AddStaticShape(entity_id_t ent, entity_pos_t x, entity_pos_t z, entity_angle_t a, entity_pos_t w, entity_pos_t h, flags_t flags, entity_id_t group, entity_id_t group2 /* = INVALID_ENTITY */)
 	{
 		fixed s, c;
 		sincos_approx(a, s, c);
 		CFixedVector2D u(c, -s);
 		CFixedVector2D v(s, c);
 
-		StaticShape shape = { ent, x, z, u, v, w/2, h/2, flags };
+		StaticShape shape = { ent, x, z, u, v, w/2, h/2, flags, group, group2 };
 		u32 id = m_StaticShapeNext++;
 		m_StaticShapes[id] = shape;
 		MakeDirtyStatic(flags);
@@ -367,6 +371,18 @@ public:
 		}
 	}
 
+	virtual void SetStaticControlGroup(tag_t tag, entity_id_t group, entity_id_t group2)
+	{
+		ENSURE(TAG_IS_VALID(tag) && TAG_IS_STATIC(tag));
+
+		if (TAG_IS_STATIC(tag))
+		{
+			StaticShape& shape = m_StaticShapes[TAG_TO_INDEX(tag)];
+			shape.group = group;
+			shape.group2 = group2;
+		}
+	}
+
 	virtual void RemoveShape(tag_t tag)
 	{
 		ENSURE(TAG_IS_VALID(tag));
@@ -533,7 +549,7 @@ bool CCmpObstructionManager::TestLine(const IObstructionTestFilter& filter, enti
 		std::map<u32, UnitShape>::iterator it = m_UnitShapes.find(unitShapes[i]);
 		ENSURE(it != m_UnitShapes.end());
 
-		if (!filter.Allowed(UNIT_INDEX_TO_TAG(it->first), it->second.flags, it->second.group))
+		if (!filter.TestShape(UNIT_INDEX_TO_TAG(it->first), it->second.flags, it->second.group, INVALID_ENTITY))
 			continue;
 
 		CFixedVector2D center(it->second.x, it->second.z);
@@ -548,7 +564,7 @@ bool CCmpObstructionManager::TestLine(const IObstructionTestFilter& filter, enti
 		std::map<u32, StaticShape>::iterator it = m_StaticShapes.find(staticShapes[i]);
 		ENSURE(it != m_StaticShapes.end());
 
-		if (!filter.Allowed(STATIC_INDEX_TO_TAG(it->first), it->second.flags, INVALID_ENTITY))
+		if (!filter.TestShape(STATIC_INDEX_TO_TAG(it->first), it->second.flags, it->second.group, it->second.group2))
 			continue;
 
 		CFixedVector2D center(it->second.x, it->second.z);
@@ -592,7 +608,7 @@ bool CCmpObstructionManager::TestStaticShape(const IObstructionTestFilter& filte
 
 	for (std::map<u32, UnitShape>::iterator it = m_UnitShapes.begin(); it != m_UnitShapes.end(); ++it)
 	{
-		if (!filter.Allowed(UNIT_INDEX_TO_TAG(it->first), it->second.flags, it->second.group))
+		if (!filter.TestShape(UNIT_INDEX_TO_TAG(it->first), it->second.flags, it->second.group, INVALID_ENTITY))
 			continue;
 
 		CFixedVector2D center1(it->second.x, it->second.z);
@@ -608,7 +624,7 @@ bool CCmpObstructionManager::TestStaticShape(const IObstructionTestFilter& filte
 
 	for (std::map<u32, StaticShape>::iterator it = m_StaticShapes.begin(); it != m_StaticShapes.end(); ++it)
 	{
-		if (!filter.Allowed(STATIC_INDEX_TO_TAG(it->first), it->second.flags, INVALID_ENTITY))
+		if (!filter.TestShape(STATIC_INDEX_TO_TAG(it->first), it->second.flags, it->second.group, it->second.group2))
 			continue;
 
 		CFixedVector2D center1(it->second.x, it->second.z);
@@ -649,7 +665,7 @@ bool CCmpObstructionManager::TestUnitShape(const IObstructionTestFilter& filter,
 
 	for (std::map<u32, UnitShape>::iterator it = m_UnitShapes.begin(); it != m_UnitShapes.end(); ++it)
 	{
-		if (!filter.Allowed(UNIT_INDEX_TO_TAG(it->first), it->second.flags, it->second.group))
+		if (!filter.TestShape(UNIT_INDEX_TO_TAG(it->first), it->second.flags, it->second.group, INVALID_ENTITY))
 			continue;
 
 		entity_pos_t r1 = it->second.r;
@@ -665,7 +681,7 @@ bool CCmpObstructionManager::TestUnitShape(const IObstructionTestFilter& filter,
 
 	for (std::map<u32, StaticShape>::iterator it = m_StaticShapes.begin(); it != m_StaticShapes.end(); ++it)
 	{
-		if (!filter.Allowed(STATIC_INDEX_TO_TAG(it->first), it->second.flags, INVALID_ENTITY))
+		if (!filter.TestShape(STATIC_INDEX_TO_TAG(it->first), it->second.flags, it->second.group, it->second.group2))
 			continue;
 
 		CFixedVector2D center1(it->second.x, it->second.z);
@@ -869,7 +885,7 @@ void CCmpObstructionManager::GetObstructionsInRange(const IObstructionTestFilter
 		std::map<u32, UnitShape>::iterator it = m_UnitShapes.find(unitShapes[i]);
 		ENSURE(it != m_UnitShapes.end());
 
-		if (!filter.Allowed(UNIT_INDEX_TO_TAG(it->first), it->second.flags, it->second.group))
+		if (!filter.TestShape(UNIT_INDEX_TO_TAG(it->first), it->second.flags, it->second.group, INVALID_ENTITY))
 			continue;
 
 		entity_pos_t r = it->second.r;
@@ -890,7 +906,7 @@ void CCmpObstructionManager::GetObstructionsInRange(const IObstructionTestFilter
 		std::map<u32, StaticShape>::iterator it = m_StaticShapes.find(staticShapes[i]);
 		ENSURE(it != m_StaticShapes.end());
 
-		if (!filter.Allowed(STATIC_INDEX_TO_TAG(it->first), it->second.flags, INVALID_ENTITY))
+		if (!filter.TestShape(STATIC_INDEX_TO_TAG(it->first), it->second.flags, it->second.group, it->second.group2))
 			continue;
 
 		entity_pos_t r = it->second.hw + it->second.hh; // overestimate the max dist of an edge from the center
diff --git a/source/simulation2/components/CCmpRallyPointRenderer.cpp b/source/simulation2/components/CCmpRallyPointRenderer.cpp
index dc16628..5e43033 100644
--- a/source/simulation2/components/CCmpRallyPointRenderer.cpp
+++ b/source/simulation2/components/CCmpRallyPointRenderer.cpp
@@ -870,7 +870,7 @@ void CCmpRallyPointRenderer::ReduceSegmentsByVisibility(std::vector<CVector2D>&
 	// process from there on until the entire line is checked. The output is the array of base nodes.
 
 	std::vector<CVector2D> newCoords;
-	StationaryObstructionFilter obstructionFilter;
+	StationaryOnlyObstructionFilter obstructionFilter;
 	entity_pos_t lineRadius = fixed::FromFloat(m_LineThickness);
 	ICmpPathfinder::pass_class_t passabilityClass = cmpPathFinder->GetPassabilityClass(m_LinePassabilityClass);
 
diff --git a/source/simulation2/components/ICmpObstruction.cpp b/source/simulation2/components/ICmpObstruction.cpp
index ab8324e..15509e9 100644
--- a/source/simulation2/components/ICmpObstruction.cpp
+++ b/source/simulation2/components/ICmpObstruction.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2011 Wildfire Games.
+/* Copyright (C) 2012 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
@@ -29,4 +29,7 @@ DEFINE_INTERFACE_METHOD_1("SetActive", void, ICmpObstruction, SetActive, bool)
 DEFINE_INTERFACE_METHOD_1("SetDisableBlockMovementPathfinding", void, ICmpObstruction, SetDisableBlockMovementPathfinding, bool)
 DEFINE_INTERFACE_METHOD_0("GetBlockMovementFlag", bool, ICmpObstruction, GetBlockMovementFlag)
 DEFINE_INTERFACE_METHOD_1("SetControlGroup", void, ICmpObstruction, SetControlGroup, entity_id_t)
+DEFINE_INTERFACE_METHOD_0("GetControlGroup", entity_id_t, ICmpObstruction, GetControlGroup)
+DEFINE_INTERFACE_METHOD_1("SetControlGroup2", void, ICmpObstruction, SetControlGroup2, entity_id_t)
+DEFINE_INTERFACE_METHOD_0("GetControlGroup2", entity_id_t, ICmpObstruction, GetControlGroup2)
 END_INTERFACE_WRAPPER(Obstruction)
diff --git a/source/simulation2/components/ICmpObstruction.h b/source/simulation2/components/ICmpObstruction.h
index f8693e7..4964b65 100644
--- a/source/simulation2/components/ICmpObstruction.h
+++ b/source/simulation2/components/ICmpObstruction.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2011 Wildfire Games.
+/* Copyright (C) 2012 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
@@ -44,6 +44,7 @@ public:
 	/**
 	 * Test whether this entity is colliding with any obstruction that are set to
 	 * block the creation of foundations.
+	 * @param ignoredEntities List of entities to ignore during the test.
 	 * @return true if foundation is valid (not obstructed)
 	 */
 	virtual bool CheckFoundation(std::string className) = 0;
@@ -70,6 +71,12 @@ public:
 	 */
 	virtual void SetControlGroup(entity_id_t group) = 0;
 
+	/// See SetControlGroup.
+	virtual entity_id_t GetControlGroup() = 0;
+
+	virtual void SetControlGroup2(entity_id_t group2) = 0;
+	virtual entity_id_t GetControlGroup2() = 0;
+
 	DECLARE_INTERFACE_TYPE(Obstruction)
 };
 
diff --git a/source/simulation2/components/ICmpObstructionManager.h b/source/simulation2/components/ICmpObstructionManager.h
index b81a6d1..9a6507c 100644
--- a/source/simulation2/components/ICmpObstructionManager.h
+++ b/source/simulation2/components/ICmpObstructionManager.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2011 Wildfire Games.
+/* Copyright (C) 2012 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
@@ -93,19 +93,24 @@ public:
 
 	/**
 	 * Register a static shape.
+	 * 
 	 * @param ent entity ID associated with this shape (or INVALID_ENTITY if none)
 	 * @param x,z coordinates of center, in world space
 	 * @param a angle of rotation (clockwise from +Z direction)
 	 * @param w width (size along X axis)
 	 * @param h height (size along Z axis)
 	 * @param flags a set of EFlags values
+	 * @param group primary control group of the shape. Must be a valid control group ID.
+	 * @param group2 Optional; secondary control group of the shape. Defaults to INVALID_ENTITY.
 	 * @return a valid tag for manipulating the shape
 	 * @see StaticShape
 	 */
-	virtual tag_t AddStaticShape(entity_id_t ent, entity_pos_t x, entity_pos_t z, entity_angle_t a, entity_pos_t w, entity_pos_t h, flags_t flags) = 0;
+	virtual tag_t AddStaticShape(entity_id_t ent, entity_pos_t x, entity_pos_t z, entity_angle_t a, 
+		entity_pos_t w, entity_pos_t h, flags_t flags, entity_id_t group, entity_id_t group2 = INVALID_ENTITY) = 0;
 
 	/**
 	 * Register a unit shape.
+	 * 
 	 * @param ent entity ID associated with this shape (or INVALID_ENTITY if none)
 	 * @param x,z coordinates of center, in world space
 	 * @param r radius of circle or half the unit's width/height
@@ -115,7 +120,8 @@ public:
 	 * @return a valid tag for manipulating the shape
 	 * @see UnitShape
 	 */
-	virtual tag_t AddUnitShape(entity_id_t ent, entity_pos_t x, entity_pos_t z, entity_angle_t r, flags_t flags, entity_id_t group) = 0;
+	virtual tag_t AddUnitShape(entity_id_t ent, entity_pos_t x, entity_pos_t z, entity_angle_t r, flags_t flags,
+		entity_id_t group) = 0;
 
 	/**
 	 * Adjust the position and angle of an existing shape.
@@ -141,6 +147,13 @@ public:
 	virtual void SetUnitControlGroup(tag_t tag, entity_id_t group) = 0;
 
 	/**
+	 * Sets the control group of a static shape.
+	 * @param tag Tag of the shape to set the control group for. Must be a valid and static shape tag.
+	 * @param group Control group entity ID.
+	 */
+	virtual void SetStaticControlGroup(tag_t tag, entity_id_t group, entity_id_t group2) = 0;
+
+	/**
 	 * Remove an existing shape. The tag will be made invalid and must not be used after this.
 	 * @param tag tag of shape (must be valid)
 	 */
@@ -162,7 +175,7 @@ public:
 
 	/**
 	 * Collision test a static square shape against the current set of shapes.
-	 * @param filter filter to restrict the shapes that are counted
+	 * @param filter filter to restrict the shapes that are being tested against
 	 * @param x X coordinate of center
 	 * @param z Z coordinate of center
 	 * @param a angle of rotation (clockwise from +Z direction)
@@ -176,12 +189,15 @@ public:
 		std::vector<entity_id_t>* out) = 0;
 
 	/**
-	 * Collision test a unit shape against the current set of shapes.
-	 * @param filter filter to restrict the shapes that are counted
-	 * @param x X coordinate of center
-	 * @param z Z coordinate of center
-	 * @param r radius (half the unit's width/height)
+	 * Collision test a unit shape against the current set of registered shapes, and optionally writes a list of the colliding
+	 * shapes' entities to an output list.
+	 * 
+	 * @param filter filter to restrict the shapes that are being tested against
+	 * @param x X coordinate of shape's center
+	 * @param z Z coordinate of shape's center
+	 * @param r radius of the shape (half the unit's width/height)
 	 * @param out if non-NULL, all colliding shapes' entities will be added to this list
+	 * 
 	 * @return true if there is a collision
 	 */
 	virtual bool TestUnitShape(const IObstructionTestFilter& filter,
@@ -274,34 +290,37 @@ public:
 	virtual ~IObstructionTestFilter() {}
 
 	/**
-	 * Return true if the shape should be counted for collisions.
+	 * Return true if the shape with the specified parameters should be tested for collisions.
 	 * This is called for all shapes that would collide, and also for some that wouldn't.
+	 * 
 	 * @param tag tag of shape being tested
 	 * @param flags set of EFlags for the shape
-	 * @param group the control group (typically the shape's unit, or the unit's formation controller, or 0)
+	 * @param group the control group of the shape (typically the shape's unit, or the unit's formation controller, or 0)
+	 * @param group2 an optional secondary control group of the shape, or INVALID_ENTITY if none specified. Currently 
+	 *               exists only for static shapes.
 	 */
-	virtual bool Allowed(tag_t tag, flags_t flags, entity_id_t group) const = 0;
+	virtual bool TestShape(tag_t tag, flags_t flags, entity_id_t group, entity_id_t group2) const = 0;
 };
 
 /**
- * Obstruction test filter that accepts all shapes.
+ * Obstruction test filter that will test against all shapes.
  */
 class NullObstructionFilter : public IObstructionTestFilter
 {
 public:
-	virtual bool Allowed(tag_t UNUSED(tag), flags_t UNUSED(flags), entity_id_t UNUSED(group)) const
+	virtual bool TestShape(tag_t UNUSED(tag), flags_t UNUSED(flags), entity_id_t UNUSED(group), entity_id_t UNUSED(group2)) const
 	{
 		return true;
 	}
 };
 
 /**
- * Obstruction test filter that accepts all non-moving shapes.
+ * Obstruction test filter that will test only against stationary (i.e. non-moving) shapes.
  */
-class StationaryObstructionFilter : public IObstructionTestFilter
+class StationaryOnlyObstructionFilter : public IObstructionTestFilter
 {
 public:
-	virtual bool Allowed(tag_t UNUSED(tag), flags_t flags, entity_id_t UNUSED(group)) const
+	virtual bool TestShape(tag_t UNUSED(tag), flags_t flags, entity_id_t UNUSED(group), entity_id_t UNUSED(group2)) const
 	{
 		return !(flags & ICmpObstructionManager::FLAG_MOVING);
 	}
@@ -309,22 +328,21 @@ public:
 
 /**
  * Obstruction test filter that reject shapes in a given control group,
- * and optionally rejects moving shapes,
- * and rejects shapes that don't block unit movement.
+ * and rejects shapes that don't block unit movement, and optionally rejects moving shapes.
  */
 class ControlGroupMovementObstructionFilter : public IObstructionTestFilter
 {
 	bool m_AvoidMoving;
 	entity_id_t m_Group;
+
 public:
 	ControlGroupMovementObstructionFilter(bool avoidMoving, entity_id_t group) :
 		m_AvoidMoving(avoidMoving), m_Group(group)
-	{
-	}
+	{}
 
-	virtual bool Allowed(tag_t UNUSED(tag), flags_t flags, entity_id_t group) const
+	virtual bool TestShape(tag_t UNUSED(tag), flags_t flags, entity_id_t group, entity_id_t group2) const
 	{
-		if (group == m_Group)
+		if (group == m_Group || (group2 != INVALID_ENTITY && group2 == m_Group))
 			return false;
 		if (!(flags & ICmpObstructionManager::FLAG_BLOCK_MOVEMENT))
 			return false;
@@ -335,7 +353,52 @@ public:
 };
 
 /**
- * Obstruction test filter that rejects a specific shape.
+ * Obstruction test filter that will test only against shapes that:
+ *     - are part of neither one of the specified control groups
+ *     - AND have at least one of the specified flags set.
+ * 
+ * The first (primary) control group to reject shapes from must be specified and valid. Set the
+ * secondary control group to INVALID_ENTITY to use only the first.
+ * 
+ * This filter is useful to e.g. allow foundations within the same control group to be placed and 
+ * constructed arbitrarily close together (e.g. for wall pieces that need to link up tightly).
+ */
+class SkipControlGroupsRequireFlagObstructionFilter : public IObstructionTestFilter
+{
+	entity_id_t m_Group;
+	entity_id_t m_Group2;
+	flags_t m_Mask;
+
+public:
+	SkipControlGroupsRequireFlagObstructionFilter(entity_id_t group1, entity_id_t group2, flags_t mask) : 
+		m_Group(group1), m_Group2(group2), m_Mask(mask)
+	{
+		// the primary control group to filter out must be valid
+		ENSURE(m_Group != INVALID_ENTITY);
+
+		// for simplicity, if m_Group2 is INVALID_ENTITY (i.e. not used), then set it equal to m_Group
+		// so that we have fewer special cases to consider in TestShape().
+		if (m_Group2 == INVALID_ENTITY)
+			m_Group2 = m_Group;
+	}
+
+	virtual bool TestShape(tag_t UNUSED(tag), flags_t flags, entity_id_t group, entity_id_t group2) const
+	{
+		// to be included in the testing, a shape must have at least one of the flags in m_Mask set, and its
+		// primary control group must be valid and must equal neither our primary nor secondary control group.
+		bool includeInTesting = ((flags & m_Mask) != 0 && group != m_Group && group != m_Group2);
+
+		// if the shape being tested has a valid secondary control group, exclude it from testing if it
+		// matches either our primary or secondary control group.
+		if (group2 != INVALID_ENTITY)
+			includeInTesting = (includeInTesting && group2 != m_Group && group2 != m_Group2);
+
+		return includeInTesting;
+	}
+};
+
+/**
+ * Obstruction test filter that will test only against shapes that do not have the specified tag set.
  */
 class SkipTagObstructionFilter : public IObstructionTestFilter
 {
@@ -345,25 +408,27 @@ public:
 	{
 	}
 
-	virtual bool Allowed(tag_t tag, flags_t UNUSED(flags), entity_id_t UNUSED(group)) const
+	virtual bool TestShape(tag_t tag, flags_t UNUSED(flags), entity_id_t UNUSED(group), entity_id_t UNUSED(group2)) const
 	{
 		return tag.n != m_Tag.n;
 	}
 };
 
 /**
- * Obstruction test filter that rejects a specific shape, and requires the given flags.
+ * Obstruction test filter that will test only against shapes that:
+ *    - do not have the specified tag
+ *    - AND have at least one of the specified flags set.
  */
-class SkipTagFlagsObstructionFilter : public IObstructionTestFilter
+class SkipTagRequireFlagsObstructionFilter : public IObstructionTestFilter
 {
 	tag_t m_Tag;
 	flags_t m_Mask;
 public:
-	SkipTagFlagsObstructionFilter(tag_t tag, flags_t mask) : m_Tag(tag), m_Mask(mask)
+	SkipTagRequireFlagsObstructionFilter(tag_t tag, flags_t mask) : m_Tag(tag), m_Mask(mask)
 	{
 	}
 
-	virtual bool Allowed(tag_t tag, flags_t flags, entity_id_t UNUSED(group)) const
+	virtual bool TestShape(tag_t tag, flags_t flags, entity_id_t UNUSED(group), entity_id_t UNUSED(group2)) const
 	{
 		return (tag.n != m_Tag.n && (flags & m_Mask) != 0);
 	}
diff --git a/source/simulation2/components/tests/test_ObstructionManager.h b/source/simulation2/components/tests/test_ObstructionManager.h
new file mode 100644
index 0000000..f327e1a
--- /dev/null
+++ b/source/simulation2/components/tests/test_ObstructionManager.h
@@ -0,0 +1,477 @@
+/* Copyright (C) 2012 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "simulation2/system/ComponentTest.h"
+
+#include "simulation2/components/ICmpObstructionManager.h"
+
+class TestCmpObstructionManager : public CxxTest::TestSuite
+{
+	typedef ICmpObstructionManager::tag_t tag_t;
+	typedef ICmpObstructionManager::ObstructionSquare ObstructionSquare;
+
+	// some variables for setting up a scene with 3 shapes
+	entity_id_t ent1, ent2, ent3; // entity IDs
+	entity_angle_t ent1a, ent2r, ent3r; // angles/radiuses
+	entity_pos_t ent1x, ent1z, ent1w, ent1h, // positions/dimensions
+	             ent2x, ent2z,
+	             ent3x, ent3z;
+	entity_id_t ent1g1, ent1g2, ent2g, ent3g; // control groups
+
+	tag_t shape1, shape2, shape3;
+	
+	ICmpObstructionManager* cmp;
+	ComponentTestHelper* testHelper;
+
+public:
+	void setUp()
+	{
+		CXeromyces::Startup();
+		CxxTest::setAbortTestOnFail(true);
+
+		// set up a simple scene with some predefined obstruction shapes
+		// (we can't position shapes on the origin because the world bounds must range 
+		// from 0 to X, so instead we'll offset things by, say, 10).
+
+		ent1 = 1;
+		ent1a = fixed::Zero();
+		ent1w = fixed::FromFloat(4);
+		ent1h = fixed::FromFloat(2);
+		ent1x = fixed::FromInt(10);
+		ent1z = fixed::FromInt(10);
+		ent1g1 = ent1;
+		ent1g2 = INVALID_ENTITY;
+
+		ent2 = 2;
+		ent2r = fixed::FromFloat(1);
+		ent2x = ent1x;
+		ent2z = ent1z;
+		ent2g = ent1g1;
+
+		ent3 = 3;
+		ent3r = fixed::FromFloat(3);
+		ent3x = ent2x;
+		ent3z = ent2z + ent2r + ent3r; // ensure it just touches the border of ent2
+		ent3g = ent3;
+
+		testHelper = new ComponentTestHelper;
+		cmp = testHelper->Add<ICmpObstructionManager>(CID_ObstructionManager, "");
+		cmp->SetBounds(fixed::FromInt(0), fixed::FromInt(0), fixed::FromInt(100), fixed::FromInt(100));
+
+		shape1 = cmp->AddStaticShape(ent1, ent1x, ent1z, ent1a, ent1w, ent1h,
+			ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION |
+			ICmpObstructionManager::FLAG_BLOCK_MOVEMENT |
+			ICmpObstructionManager::FLAG_MOVING, ent1g1, ent1g2);
+
+		shape2 = cmp->AddUnitShape(ent2, ent2x, ent2z, ent2r,
+			ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION |
+			ICmpObstructionManager::FLAG_BLOCK_FOUNDATION, ent2g);
+
+		shape3 = cmp->AddUnitShape(ent3, ent3x, ent3z, ent3r,
+			ICmpObstructionManager::FLAG_BLOCK_MOVEMENT |
+			ICmpObstructionManager::FLAG_BLOCK_FOUNDATION, ent3g);
+	}
+
+	void tearDown()
+	{
+		delete testHelper;
+		cmp = NULL; // not our responsibility to deallocate
+
+		CXeromyces::Terminate();
+	}
+
+	/**
+	 * Verifies the collision testing procedure. Collision-tests some simple shapes against the shapes registered in
+	 * the scene, and verifies the result of the test against the expected value.
+	 */
+	void test_collision_simple()
+	{
+		std::vector<entity_id_t> out;
+		NullObstructionFilter nullFilter;
+		
+		// Collision-test a simple shape nested inside shape3 against all shapes in the scene. Since the tested shape
+		// overlaps only with shape 3, we should find only shape 3 in the result.
+		
+		cmp->TestUnitShape(nullFilter, ent3x, ent3z, fixed::FromInt(1), &out);
+		TS_ASSERT_EQUALS(1, out.size());
+		TS_ASSERT_EQUALS(ent3, out[0]);
+		out.clear();
+
+		cmp->TestStaticShape(nullFilter, ent3x, ent3z, fixed::Zero(), fixed::FromInt(1), fixed::FromInt(1), &out);
+		TS_ASSERT_EQUALS(1, out.size());
+		TS_ASSERT_EQUALS(ent3, out[0]);
+		out.clear();
+
+		// Similarly, collision-test a simple shape nested inside both shape1 and shape2. Since the tested shape overlaps
+		// only with shapes 1 and 2, those are the only ones we should find in the result.
+		
+		cmp->TestUnitShape(nullFilter, ent2x, ent2z, ent2r/2, &out);
+		TS_ASSERT_EQUALS(2, out.size());
+		TS_ASSERT_VECTOR_CONTAINS(out, ent1);
+		TS_ASSERT_VECTOR_CONTAINS(out, ent2);
+		out.clear();
+
+		cmp->TestStaticShape(nullFilter, ent2x, ent2z, fixed::Zero(), ent2r, ent2r, &out);
+		TS_ASSERT_EQUALS(2, out.size());
+		TS_ASSERT_VECTOR_CONTAINS(out, ent1);
+		TS_ASSERT_VECTOR_CONTAINS(out, ent2);
+		out.clear();
+	}
+
+	/**
+	 * Verifies the behaviour of the null obstruction filter. Tests with this filter will be performed against all
+	 * registered shapes.
+	 */
+	void test_collision_filter_null()
+	{
+		std::vector<entity_id_t> out;
+
+		// Collision test a scene-covering shape against all shapes in the scene. We should find all registered shapes
+		// in the result.
+
+		NullObstructionFilter nullFilter;
+
+		cmp->TestUnitShape(nullFilter, ent1x, ent1z, fixed::FromInt(10), &out);
+		TS_ASSERT_EQUALS(3, out.size());
+		TS_ASSERT_VECTOR_CONTAINS(out, ent1);
+		TS_ASSERT_VECTOR_CONTAINS(out, ent2);
+		TS_ASSERT_VECTOR_CONTAINS(out, ent3);
+		out.clear();
+
+		cmp->TestStaticShape(nullFilter, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
+		TS_ASSERT_EQUALS(3, out.size());
+		TS_ASSERT_VECTOR_CONTAINS(out, ent1);
+		TS_ASSERT_VECTOR_CONTAINS(out, ent2);
+		TS_ASSERT_VECTOR_CONTAINS(out, ent3);
+		out.clear();
+	}
+
+	/**
+	 * Verifies the behaviour of the StationaryOnlyObstructionFilter. Tests with this filter will be performed only
+	 * against non-moving (stationary) shapes.
+	 */
+	void test_collision_filter_stationary_only()
+	{
+		std::vector<entity_id_t> out;
+
+		// Collision test a scene-covering shape against all shapes in the scene, but skipping shapes that are moving,
+		// i.e. shapes that have the MOVING flag. Since only shape 1 is flagged as moving, we should find 
+		// shapes 2 and 3 in each case.
+
+		StationaryOnlyObstructionFilter ignoreMoving;
+
+		cmp->TestUnitShape(ignoreMoving, ent1x, ent1z, fixed::FromInt(10), &out);
+		TS_ASSERT_EQUALS(2, out.size());
+		TS_ASSERT_VECTOR_CONTAINS(out, ent2);
+		TS_ASSERT_VECTOR_CONTAINS(out, ent3);
+		out.clear();
+
+		cmp->TestStaticShape(ignoreMoving, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
+		TS_ASSERT_EQUALS(2, out.size());
+		TS_ASSERT_VECTOR_CONTAINS(out, ent2);
+		TS_ASSERT_VECTOR_CONTAINS(out, ent3);
+		out.clear();
+	}
+
+	/**
+	 * Verifies the behaviour of the SkipTagObstructionFilter. Tests with this filter will be performed against 
+	 * all registered shapes that do not have the specified tag set.
+	 */
+	void test_collision_filter_skip_tag()
+	{
+		std::vector<entity_id_t> out;
+
+		// Collision-test shape 2's obstruction shape against all shapes in the scene, but skipping tests against
+		// shape 2. Since shape 2 overlaps only with shape 1, we should find only shape 1's entity ID in the result.
+
+		SkipTagObstructionFilter ignoreShape2(shape2);
+
+		cmp->TestUnitShape(ignoreShape2, ent2x, ent2z, ent2r/2, &out);
+		TS_ASSERT_EQUALS(1, out.size());
+		TS_ASSERT_EQUALS(ent1, out[0]);
+		out.clear();
+
+		cmp->TestStaticShape(ignoreShape2, ent2x, ent2z, fixed::Zero(), ent2r, ent2r, &out);
+		TS_ASSERT_EQUALS(1, out.size());
+		TS_ASSERT_EQUALS(ent1, out[0]);
+		out.clear();
+	}
+
+	/**
+	 * Verifies the behaviour of the SkipTagFlagsObstructionFilter. Tests with this filter will be performed against
+	 * all registered shapes that do not have the specified tag set, and that have at least one of required flags set.
+	 */
+	void test_collision_filter_skip_tag_require_flag()
+	{
+		std::vector<entity_id_t> out;
+
+		// Collision-test a scene-covering shape against all shapes in the scene, but skipping tests against shape 1
+		// and requiring the BLOCK_MOVEMENT flag. Since shape 1 is being ignored and shape 2 does not have the required
+		// flag, we should find only shape 3 in the results.
+
+		SkipTagRequireFlagsObstructionFilter skipShape1RequireBlockMovement(shape1, ICmpObstructionManager::FLAG_BLOCK_MOVEMENT);
+
+		cmp->TestUnitShape(skipShape1RequireBlockMovement, ent1x, ent1z, fixed::FromInt(10), &out);
+		TS_ASSERT_EQUALS(1, out.size());
+		TS_ASSERT_EQUALS(ent3, out[0]);
+		out.clear();
+
+		cmp->TestStaticShape(skipShape1RequireBlockMovement, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
+		TS_ASSERT_EQUALS(1, out.size());
+		TS_ASSERT_EQUALS(ent3, out[0]);
+		out.clear();
+
+		// If we now do the same test, but require at least one of the entire set of available filters, we should find
+		// all shapes that are not shape 1 and that have at least one flag set. Since all shapes in our testing scene
+		// have at least one flag set, we should find shape 2 and shape 3 in the results.
+
+		SkipTagRequireFlagsObstructionFilter skipShape1RequireAnyFlag(shape1, (ICmpObstructionManager::flags_t) -1);
+
+		cmp->TestUnitShape(skipShape1RequireAnyFlag, ent1x, ent1z, fixed::FromInt(10), &out);
+		TS_ASSERT_EQUALS(2, out.size());
+		TS_ASSERT_VECTOR_CONTAINS(out, ent2);
+		TS_ASSERT_VECTOR_CONTAINS(out, ent3);
+		out.clear();
+
+		cmp->TestStaticShape(skipShape1RequireAnyFlag, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
+		TS_ASSERT_EQUALS(2, out.size());
+		TS_ASSERT_VECTOR_CONTAINS(out, ent2);
+		TS_ASSERT_VECTOR_CONTAINS(out, ent3);
+		out.clear();
+
+		// And if we now do the same test yet again, but specify an empty set of flags, then it becomes impossible for
+		// any shape to have at least one of the required flags, and we should hence find no shapes in the result.
+		
+		SkipTagRequireFlagsObstructionFilter skipShape1RejectAll(shape1, 0U);
+		
+		cmp->TestUnitShape(skipShape1RejectAll, ent1x, ent1z, fixed::FromInt(10), &out);
+		TS_ASSERT_EQUALS(0, out.size());
+		out.clear();
+
+		cmp->TestStaticShape(skipShape1RejectAll, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
+		TS_ASSERT_EQUALS(0, out.size());
+		out.clear();
+	}
+
+	/**
+	 * Verifies the behaviour of SkipControlGroupsRequireFlagObstructionFilter. Tests with this filter will be performed 
+	 * against all registered shapes that are members of neither specified control groups, and that have at least one of
+	 * the specified flags set.
+	 */
+	void test_collision_filter_skip_controlgroups_require_flag()
+	{
+		std::vector<entity_id_t> out;
+
+		// Collision-test a shape that overlaps the entire scene, but ignoring shapes from shape1's control group
+		// (which also includes shape 2), and requiring that either the BLOCK_FOUNDATION or the 
+		// BLOCK_CONSTRUCTION flag is set, or both. Since shape 1 and shape 2 both belong to shape 1's control
+		// group, and shape 3 has the BLOCK_FOUNDATION flag (but not BLOCK_CONSTRUCTION), we should find only 
+		// shape 3 in the result.
+
+		SkipControlGroupsRequireFlagObstructionFilter skipGroup1ReqFoundConstr(ent1g1, INVALID_ENTITY,
+			ICmpObstructionManager::FLAG_BLOCK_FOUNDATION | ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION);
+
+		cmp->TestUnitShape(skipGroup1ReqFoundConstr, ent1x, ent1z, fixed::FromInt(10), &out);
+		TS_ASSERT_EQUALS(1, out.size());
+		TS_ASSERT_EQUALS(ent3, out[0]);
+		out.clear();
+
+		cmp->TestStaticShape(skipGroup1ReqFoundConstr, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
+		TS_ASSERT_EQUALS(1, out.size());
+		TS_ASSERT_EQUALS(ent3, out[0]);
+		out.clear();
+
+		// Perform the same test, but now also exclude shape 3's control group (in addition to shape 1's control
+		// group). Despite shape 3 having at least one of the required flags set, it should now also be ignored,
+		// yielding an empty result set.
+		
+		SkipControlGroupsRequireFlagObstructionFilter skipGroup1And3ReqFoundConstr(ent1g1, ent3g,
+			ICmpObstructionManager::FLAG_BLOCK_FOUNDATION | ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION);
+
+		cmp->TestUnitShape(skipGroup1And3ReqFoundConstr, ent1x, ent1z, fixed::FromInt(10), &out);
+		TS_ASSERT_EQUALS(0, out.size());
+		out.clear();
+
+		cmp->TestStaticShape(skipGroup1And3ReqFoundConstr, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
+		TS_ASSERT_EQUALS(0, out.size());
+		out.clear();
+
+		// Same test, but this time excluding only shape 3's control group, and requiring any of the available flags
+		// to be set. Since both shape 1 and shape 2 have at least one flag set and are both in a different control 
+		// group, we should find them in the result.
+		
+		SkipControlGroupsRequireFlagObstructionFilter skipGroup3RequireAnyFlag(ent3g, INVALID_ENTITY,
+			(ICmpObstructionManager::flags_t) -1);
+
+		cmp->TestUnitShape(skipGroup3RequireAnyFlag, ent1x, ent1z, fixed::FromInt(10), &out);
+		TS_ASSERT_EQUALS(2, out.size());
+		TS_ASSERT_VECTOR_CONTAINS(out, ent1);
+		TS_ASSERT_VECTOR_CONTAINS(out, ent2);
+		out.clear();
+
+		cmp->TestStaticShape(skipGroup3RequireAnyFlag, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
+		TS_ASSERT_EQUALS(2, out.size());
+		TS_ASSERT_VECTOR_CONTAINS(out, ent1);
+		TS_ASSERT_VECTOR_CONTAINS(out, ent2);
+		out.clear();
+
+		// Finally, the same test as the one directly above, now with an empty set of required flags. Since it now becomes
+		// impossible for shape 1 and shape 2 to have at least one of the required flags set, and shape 3 is excluded by
+		// virtue of the control group filtering, we should find an empty result.
+
+		SkipControlGroupsRequireFlagObstructionFilter skipGroup3RequireNoFlags(ent3g, INVALID_ENTITY, 0U);
+
+		cmp->TestUnitShape(skipGroup3RequireNoFlags, ent1x, ent1z, fixed::FromInt(10), &out);
+		TS_ASSERT_EQUALS(0, out.size());
+		out.clear();
+
+		cmp->TestStaticShape(skipGroup3RequireNoFlags, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
+		TS_ASSERT_EQUALS(0, out.size());
+		out.clear();
+
+		// ------------------------------------------------------------------------------------
+
+		// In the tests up until this point, the shapes have all been filtered out based on their primary control group.
+		// Now, to verify that shapes are also filtered out based on their secondary control groups, add a fourth shape 
+		// with arbitrarily-chosen dual control groups, and also change shape 1's secondary control group to another 
+		// arbitrarily-chosen control group. Then, do a scene-covering collision test while filtering out a combination 
+		// of shape 1's secondary control group, and one of shape 4's control groups. We should find neither ent1 nor ent4
+		// in the result.
+
+		entity_id_t ent4 = 4,
+		            ent4g1 = 17,
+		            ent4g2 = 19,
+					ent1g2_new = 18; // new secondary control group for entity 1
+		entity_pos_t ent4x = fixed::FromInt(4),
+		             ent4z = fixed::Zero(),
+		             ent4w = fixed::FromInt(1),
+		             ent4h = fixed::FromInt(1);
+		entity_angle_t ent4a = fixed::FromDouble(M_PI/3);
+
+		cmp->AddStaticShape(ent4, ent4x, ent4z, ent4a, ent4w, ent4h, ICmpObstructionManager::FLAG_BLOCK_PATHFINDING, ent4g1, ent4g2);
+		cmp->SetStaticControlGroup(shape1, ent1g1, ent1g2_new);
+
+		// Exclude shape 1's and shape 4's secondary control groups from testing, and require any available flag to be set.
+		// Since neither shape 2 nor shape 3 are part of those control groups and both have at least one available flag set,
+		// the results should only those two shapes' entities.
+
+		SkipControlGroupsRequireFlagObstructionFilter skipGroup1SecAnd4SecRequireAny(ent1g2_new, ent4g2,
+			(ICmpObstructionManager::flags_t) -1);
+
+		cmp->TestUnitShape(skipGroup1SecAnd4SecRequireAny, ent1x, ent1z, fixed::FromInt(10), &out);
+		TS_ASSERT_EQUALS(2, out.size());
+		TS_ASSERT_VECTOR_CONTAINS(out, ent2);
+		TS_ASSERT_VECTOR_CONTAINS(out, ent3);
+		out.clear();
+
+		cmp->TestStaticShape(skipGroup1SecAnd4SecRequireAny, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
+		TS_ASSERT_EQUALS(2, out.size());
+		TS_ASSERT_VECTOR_CONTAINS(out, ent2);
+		TS_ASSERT_VECTOR_CONTAINS(out, ent3);
+		out.clear();
+
+		// Same as the above, but now exclude shape 1's secondary and shape 4's primary control group, while still requiring 
+		// any available flag to be set. (Note that the test above used shape 4's secondary control group). Results should 
+		// remain the same.
+		
+		SkipControlGroupsRequireFlagObstructionFilter skipGroup1SecAnd4PrimRequireAny(ent1g2_new, ent4g1,
+			(ICmpObstructionManager::flags_t) -1);
+
+		cmp->TestUnitShape(skipGroup1SecAnd4PrimRequireAny, ent1x, ent1z, fixed::FromInt(10), &out);
+		TS_ASSERT_EQUALS(2, out.size());
+		TS_ASSERT_VECTOR_CONTAINS(out, ent2);
+		TS_ASSERT_VECTOR_CONTAINS(out, ent3);
+		out.clear();
+
+		cmp->TestStaticShape(skipGroup1SecAnd4PrimRequireAny, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
+		TS_ASSERT_EQUALS(2, out.size());
+		TS_ASSERT_VECTOR_CONTAINS(out, ent2);
+		TS_ASSERT_VECTOR_CONTAINS(out, ent3);
+		out.clear();
+
+		cmp->SetStaticControlGroup(shape1, ent1g1, ent1g2); // restore shape 1's original secondary control group
+	}
+
+	void test_adjacent_shapes()
+	{
+		std::vector<entity_id_t> out;
+		NullObstructionFilter nullFilter;
+		SkipTagObstructionFilter ignoreShape1(shape1);
+		SkipTagObstructionFilter ignoreShape2(shape2);
+		SkipTagObstructionFilter ignoreShape3(shape3);
+
+		// Collision-test a shape that is perfectly adjacent to shape3. This should be counted as a hit according to
+		// the code at the time of writing.
+		
+		entity_angle_t ent4a = fixed::FromDouble(M_PI); // rotated 180 degrees, should not affect collision test
+		entity_pos_t ent4w = fixed::FromInt(2),
+		             ent4h = fixed::FromInt(1),
+		             ent4x = ent3x + ent3r + ent4w/2, // make ent4 adjacent to ent3
+		             ent4z = ent3z;
+
+		cmp->TestStaticShape(nullFilter, ent4x, ent4z, ent4a, ent4w, ent4h, &out);
+		TS_ASSERT_EQUALS(1, out.size());
+		TS_ASSERT_EQUALS(ent3, out[0]);
+		out.clear();
+
+		cmp->TestUnitShape(nullFilter, ent4x, ent4z, ent4w/2, &out);
+		TS_ASSERT_EQUALS(1, out.size());
+		TS_ASSERT_EQUALS(ent3, out[0]);
+		out.clear();
+
+		// now do the same tests, but move the shape a little bit to the right so that it doesn't touch anymore
+
+		cmp->TestStaticShape(nullFilter, ent4x + fixed::FromFloat(1e-5f), ent4z, ent4a, ent4w, ent4h, &out);
+		TS_ASSERT_EQUALS(0, out.size());
+		out.clear();
+
+		cmp->TestUnitShape(nullFilter, ent4x + fixed::FromFloat(1e-5f), ent4z, ent4w/2, &out);
+		TS_ASSERT_EQUALS(0, out.size());
+		out.clear();
+	}
+
+	/**
+	 * Verifies that fetching the registered shapes from the obstruction manager yields the correct results.
+	 */
+	void test_get_obstruction()
+	{
+		ObstructionSquare obSquare1 = cmp->GetObstruction(shape1);
+		ObstructionSquare obSquare2 = cmp->GetObstruction(shape2);
+		ObstructionSquare obSquare3 = cmp->GetObstruction(shape3);
+
+		TS_ASSERT_EQUALS(obSquare1.hh, ent1h/2);
+		TS_ASSERT_EQUALS(obSquare1.hw, ent1w/2);
+		TS_ASSERT_EQUALS(obSquare1.x, ent1x);
+		TS_ASSERT_EQUALS(obSquare1.z, ent1z);
+		TS_ASSERT_EQUALS(obSquare1.u, CFixedVector2D(fixed::FromInt(1), fixed::FromInt(0)));
+		TS_ASSERT_EQUALS(obSquare1.v, CFixedVector2D(fixed::FromInt(0), fixed::FromInt(1)));
+
+		TS_ASSERT_EQUALS(obSquare2.hh, ent2r);
+		TS_ASSERT_EQUALS(obSquare2.hw, ent2r);
+		TS_ASSERT_EQUALS(obSquare2.x, ent2x);
+		TS_ASSERT_EQUALS(obSquare2.z, ent2z);
+		TS_ASSERT_EQUALS(obSquare2.u, CFixedVector2D(fixed::FromInt(1), fixed::FromInt(0)));
+		TS_ASSERT_EQUALS(obSquare2.v, CFixedVector2D(fixed::FromInt(0), fixed::FromInt(1)));
+
+		TS_ASSERT_EQUALS(obSquare3.hh, ent3r);
+		TS_ASSERT_EQUALS(obSquare3.hw, ent3r);
+		TS_ASSERT_EQUALS(obSquare3.x, ent3x);
+		TS_ASSERT_EQUALS(obSquare3.z, ent3z);
+		TS_ASSERT_EQUALS(obSquare3.u, CFixedVector2D(fixed::FromInt(1), fixed::FromInt(0)));
+		TS_ASSERT_EQUALS(obSquare3.v, CFixedVector2D(fixed::FromInt(0), fixed::FromInt(1)));
+	}
+};
\ No newline at end of file
diff --git a/source/simulation2/helpers/Selection.cpp b/source/simulation2/helpers/Selection.cpp
index b33ab0d..7ab3259 100644
--- a/source/simulation2/helpers/Selection.cpp
+++ b/source/simulation2/helpers/Selection.cpp
@@ -27,6 +27,7 @@
 #include "simulation2/components/ICmpTemplateManager.h"
 #include "simulation2/components/ICmpSelectable.h"
 #include "simulation2/components/ICmpVisual.h"
+#include "ps/CLogger.h"
 
 std::vector<entity_id_t> EntitySelection::PickEntitiesAtPoint(CSimulation2& simulation, const CCamera& camera, int screenX, int screenY, player_id_t player, bool allowEditorSelectables)
 {
@@ -161,7 +162,9 @@ std::vector<entity_id_t> EntitySelection::PickEntitiesInRect(CSimulation2& simul
 	return hitEnts;
 }
 
-std::vector<entity_id_t> EntitySelection::PickSimilarEntities(CSimulation2& simulation, const CCamera& camera, const std::string& templateName, player_id_t owner, bool includeOffScreen, bool matchRank, bool allowEditorSelectables)
+std::vector<entity_id_t> EntitySelection::PickSimilarEntities(CSimulation2& simulation, const CCamera& camera, 
+	const std::string& templateName, player_id_t owner, bool includeOffScreen, bool matchRank,
+	bool allowEditorSelectables, bool allowFoundations)
 {
 	CmpPtr<ICmpTemplateManager> cmpTemplateManager(simulation, SYSTEM_ENTITY);
 	CmpPtr<ICmpRangeManager> cmpRangeManager(simulation, SYSTEM_ENTITY);
@@ -179,8 +182,11 @@ std::vector<entity_id_t> EntitySelection::PickSimilarEntities(CSimulation2& simu
 
 		if (matchRank)
 		{
-			// Exact template name matching
-			if (cmpTemplateManager->GetCurrentTemplateName(ent) != templateName)
+			// Exact template name matching, optionally also allowing foundations
+			std::string curTemplateName = cmpTemplateManager->GetCurrentTemplateName(ent);
+			bool matches = (curTemplateName == templateName ||
+			                (allowFoundations && curTemplateName.substr(0, 11) == "foundation|" && curTemplateName.substr(11) == templateName));
+			if (!matches)
 				continue;
 		}
 
diff --git a/source/simulation2/helpers/Selection.h b/source/simulation2/helpers/Selection.h
index d3a6a8e..484ee08 100644
--- a/source/simulation2/helpers/Selection.h
+++ b/source/simulation2/helpers/Selection.h
@@ -76,12 +76,15 @@ std::vector<entity_id_t> PickEntitiesInRect(CSimulation2& simulation, const CCam
  * @param matchRank if true, only entities that exactly match templateName will be selected,
  *	else entities with matching SelectionGroupName will be selected.
  * @param allowEditorSelectables if true, all entities with the ICmpSelectable interface
- *	will be selected (including decorative actors), else only those selectable ingame.
+ *	will be selected (including decorative actors), else only those selectable in-game.
+ * @param allowFoundations if true, foundations are also included in the results. Only takes
+ *  effect when matchRank = true.
  *
  * @return unordered list of selected entities.
  * @see ICmpIdentity
  */
-std::vector<entity_id_t> PickSimilarEntities(CSimulation2& simulation, const CCamera& camera, const std::string& templateName, player_id_t owner, bool includeOffScreen, bool matchRank, bool allowEditorSelectables);
+std::vector<entity_id_t> PickSimilarEntities(CSimulation2& simulation, const CCamera& camera, const std::string& templateName,
+	player_id_t owner, bool includeOffScreen, bool matchRank, bool allowEditorSelectables, bool allowFoundations);
 
 } // namespace
 
diff --git a/source/tools/atlas/GameInterface/Handlers/ObjectHandlers.cpp b/source/tools/atlas/GameInterface/Handlers/ObjectHandlers.cpp
index 4567564..d36b2c9 100644
--- a/source/tools/atlas/GameInterface/Handlers/ObjectHandlers.cpp
+++ b/source/tools/atlas/GameInterface/Handlers/ObjectHandlers.cpp
@@ -488,7 +488,7 @@ QUERYHANDLER(PickSimilarObjects)
 	if (cmpOwnership)
 		owner = cmpOwnership->GetOwner();
 
-	msg->ids = EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, owner, false, true, true);
+	msg->ids = EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, owner, false, true, true, false);
 }
 
 

