Index: binaries/data/mods/public/simulation/components/Formation.js
===================================================================
--- binaries/data/mods/public/simulation/components/Formation.js	(revision 16780)
+++ binaries/data/mods/public/simulation/components/Formation.js	(working copy)
@@ -548,8 +548,16 @@
 		xMin = Math.min(xMin, offset.x);
 		yMin = Math.min(yMin, offset.y);
 	}
-	this.width = xMax - xMin;
-	this.depth = yMax - yMin;
+	this.width = xMax - xMin + 4;// TODO take formation width + passClass clearance
+	this.depth = yMax - yMin + 4;
+
+	// set an obstruction size to calculate collisions
+	var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
+	if (cmpObstruction)
+		cmpObstruction.SetSize(this.depth, this.width);
+	var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
+	if (cmpUnitMotion)
+		cmpUnitMotion.SetAllowedDeviation(this.width);
 };
 
 Formation.prototype.MoveToMembersCenter = function()
@@ -878,6 +886,7 @@
 {
 	var maxRadius = 0;
 	var minSpeed = Infinity;
+	var runSpeed = 0;
 
 	for each (var ent in this.members)
 	{
@@ -887,13 +896,18 @@
 
 		var cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion);
 		if (cmpUnitMotion)
+		{
 			minSpeed = Math.min(minSpeed, cmpUnitMotion.GetWalkSpeed());
+			runSpeed += cmpUnitMotion.GetRunSpeed();
+		}
 	}
+	runSpeed /= this.members.length;
 	minSpeed *= this.GetSpeedMultiplier();
 
 	var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
 	cmpUnitMotion.SetUnitRadius(maxRadius);
 	cmpUnitMotion.SetSpeed(minSpeed);
+	cmpUnitMotion.SetRunSpeed(runSpeed);
 
 	// TODO: we also need to do something about PassabilityClass, CostClass
 };
Index: binaries/data/mods/public/simulation/components/UnitMotionFormation.js
===================================================================
--- binaries/data/mods/public/simulation/components/UnitMotionFormation.js	(revision 0)
+++ binaries/data/mods/public/simulation/components/UnitMotionFormation.js	(working copy)
@@ -0,0 +1,249 @@
+function UnitMotionFormation() {}
+
+UnitMotionFormation.prototype.Schema =
+	"<empty/>";
+
+UnitMotionFormation.prototype.Init = function()
+{
+	this.goal = null;
+	this.pathTicket = 0;
+	this.passClass = 0;
+	this.path = [];
+	this.speed = 1;
+	this.runSpeed = 1;
+	this.pathIdx = -1;
+	this.state = "IDLE";
+	this.allowedDeviation = 8;
+
+	var cmpPathfinder = Engine.QueryInterface(SYSTEM_ENTITY, IID_Pathfinder);
+	this.passClass = cmpPathfinder.GetPassabilityClass("default");
+};
+
+UnitMotionFormation.prototype.SetPassabilityClassName = function(name)
+{
+	var cmpPathfinder = Engine.QueryInterface(SYSTEM_ENTITY, IID_Pathfinder);
+	this.passClass = cmpPathfinder.GetPassabilityClass(name);
+};
+
+// Ignore, unit radius is not important anymore, only the passability class is
+UnitMotionFormation.prototype.SetUnitRadius = function() {};
+
+UnitMotionFormation.prototype.SetAllowedDeviation = function(allowedDeviation)
+{
+	this.allowedDeviation = allowedDeviation;
+};
+
+UnitMotionFormation.prototype.IsMoving = function()
+{
+	return this.pathIdx >= 0;
+};
+UnitMotionFormation.prototype.SetSpeed = function(speed)
+{
+	this.speed = speed;
+};
+UnitMotionFormation.prototype.SetRunSpeed = function(speed)
+{
+	this.runSpeed = speed;
+};
+UnitMotionFormation.prototype.MoveToPointRange = function(x, z, minRange, maxRange)
+{
+	this.goal = {
+		"type": 0,
+		"x": x, // target x
+		"z": z, // target z
+		"u": {"x": 1, "y": 0}, // first unit axis
+		"v": {"x": 0, "y": 1}, // second unit axis
+		"hw": maxRange, // for a circle, hw is range
+		"hh": maxRange,
+		"maxdist": 0 // maxdist between waypoints, 0 -> unlimited
+	}
+	var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+	if (!cmpPosition || !cmpPosition.IsInWorld())
+		return false;
+
+	var pos = cmpPosition.GetPosition2D();
+
+	var cmpPathfinder = Engine.QueryInterface(SYSTEM_ENTITY, IID_Pathfinder);
+	this.pathTicket = cmpPathfinder.ComputePathAsync(pos.x, pos.y, this.goal, this.passClass, this.entity);
+
+	this.state = "PATH_REQUESTED";
+	return true;
+};
+
+UnitMotionFormation.prototype.OnPathResult = function(msg)
+{
+	if (msg.ticket != this.pathTicket)
+		return;
+	if (!msg.path.length)
+		return;
+
+	var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+	if (!cmpPosition || !cmpPosition.IsInWorld())
+		return;
+
+	var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
+	if (!cmpFormation)
+		return;
+	// path generalisation
+	var size = Math.min(cmpFormation.GetSize().width, cmpFormation.GetSize().depth);
+	var pos = msg.path[0];
+	this.path = [pos];
+	for (var i = 1; i < msg.path.length; ++i)
+	{
+		var dx = pos.x - msg.path[i].x;
+		var dy = pos.y - msg.path[i].y;
+		if (dx * dx + dy * dy < size * size)
+			continue;
+		pos = msg.path[i];
+		this.path.push(pos);
+	}
+	this.pathIdx = this.path.length - 1;
+	// add the current position for reference
+	pos = cmpPosition.GetPosition2D();
+	this.path.push({"x" : pos.x, "y": pos.y});
+
+	var direction = Vector2D.clone(this.path[this.pathIdx]).sub(pos);
+	cmpPosition.TurnTo(Math.atan2(direction.x, direction.y));
+
+	this.state = "FOLLOW_LINE";
+	Engine.PostMessage(this.entity, MT_MotionChanged, { "starting": true, "error": false });
+};
+
+UnitMotionFormation.prototype.OnUpdate = function(msg)
+{
+	if (!this.IsMoving())
+		return;
+	if (this.state == "FOLLOW_LINE")
+		this.FollowLine(msg.turnLength);
+	else if (this.state == "TAKE_TURN")
+		this.TakeTurn(msg.turnLength);
+};
+
+UnitMotionFormation.prototype.FollowLine = function(turnLength)
+{
+	var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+	if (!cmpPosition || !cmpPosition.IsInWorld())
+		return;
+
+	var pos = cmpPosition.GetPosition2D();
+
+	// project current position to original path
+	var target = this.path[this.pathIdx];
+	var source = this.path[this.pathIdx + 1];
+	var direction = Vector2D.clone(target).sub(source);
+	var posVec = Vector2D.clone(pos).sub(source);
+	var pathPosition = Vector2D.clone(direction).mult(posVec.dot(direction) / direction.lengthSquared());
+
+	// Get the distance on the original path
+	var pathToWalk = Vector2D.clone(direction).sub(pathPosition);
+	var distanceToTarget = pathToWalk.length();
+	var distanceToWalk = this.speed * turnLength;
+
+	if (distanceToTarget < distanceToWalk) // just walk straight on, and start turning
+	{
+		pos.add(pathToWalk);
+		cmpPosition.MoveTo(pos.x, pos.y);
+		// if this is the last target, stop moving
+		if (this.pathIdx)
+		{
+			// else, take a turn
+			var usedTime = distanceToTarget / this.speed;
+			this.state = "TAKE_TURN";
+			this.TakeTurn(turnLength - usedTime);
+		}
+		else
+		{
+			this.pathIdx--;
+			Engine.PostMessage(this.entity, MT_MotionChanged, { "starting": false, "error": false });
+			this.state = "IDLE";
+		}
+		return;
+	}
+
+	var currentDeviation = posVec.distanceTo(pathPosition);
+	if (this.pathIdx == 0 && distanceToTarget <= currentDeviation)
+	{
+		var pathToTarget = Vector2D.clone(target).sub(pos).normalize().mult(distanceToWalk);
+		pos.add(pathToTarget);
+		cmpPosition.MoveTo(pos.x, pos.y);
+		return;
+	}
+
+	// Find the next position
+	// scale the path to walk to include the walkable distance
+	var centerPathToWalk = Vector2D.clone(pathToWalk).mult(distanceToWalk / distanceToTarget);
+	// expand the rotation matrix for the 45deg rotations
+	var leftPathToWalk = new Vector2D(centerPathToWalk.x - centerPathToWalk.y, centerPathToWalk.x + centerPathToWalk.y);
+	var rightPathToWalk = new Vector2D(centerPathToWalk.x + centerPathToWalk.y, -centerPathToWalk.x + centerPathToWalk.y);
+	
+	var newCenterPosVec = Vector2D.clone(posVec).add(centerPathToWalk);
+	var newLeftPosVec = Vector2D.clone(posVec).add(leftPathToWalk);
+	var newRightPosVec = Vector2D.clone(posVec).add(rightPathToWalk);
+
+	// project the center to the original path
+	var centerPathPosition = Vector2D.clone(direction).mult(newCenterPosVec.dot(direction) / direction.lengthSquared());
+
+	var allowLeft = newLeftPosVec.distanceToSquared(centerPathPosition) < this.allowedDeviation * this.allowedDeviation;
+	var allowRight = newRightPosVec.distanceToSquared(centerPathPosition) < this.allowedDeviation * this.allowedDeviation;
+
+	// compare the obstructions
+	var cmpPathfinder = Engine.QueryInterface(SYSTEM_ENTITY, IID_Pathfinder);
+	var centerObstructedTiles = cmpPathfinder.GetObstructedTiles(centerPathToWalk, this.entity, this.passClass);
+	var leftObstructedTiles = Infinity;
+	var rightObstructedTiles = Infinity;
+	if (allowLeft)
+		leftObstructedTiles = cmpPathfinder.GetObstructedTiles(leftPathToWalk, this.entity, this.passClass);
+	if (allowRight)
+		rightObstructedTiles = cmpPathfinder.GetObstructedTiles(rightPathToWalk, this.entity, this.passClass);
+
+	// choose the best of the three positions
+	var min = Math.min(centerObstructedTiles, leftObstructedTiles, rightObstructedTiles);
+	if (min == centerObstructedTiles)
+		pos.add(centerPathToWalk);
+	else if (min == leftObstructedTiles)
+		pos.add(leftPathToWalk);
+	else if (min == rightObstructedTiles)
+		pos.add(rightPathToWalk);
+
+	cmpPosition.MoveTo(pos.x, pos.y);
+};
+
+UnitMotionFormation.prototype.TakeTurn = function(turnLength)
+{
+	var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+	var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
+	if (!cmpPosition || !cmpPosition.IsInWorld() || !cmpFormation)
+		return;
+	var formationSize = Math.max(cmpFormation.GetSize().width, cmpFormation.GetSize().depth) / 2;
+	var maxTurnAngle = turnLength * this.runSpeed / formationSize;
+	var currentAngle = cmpPosition.GetRotation().y;
+	var newSource = Vector2D.clone(this.path[this.pathIdx]);
+	var newTarget = Vector2D.clone(this.path[this.pathIdx - 1]);
+	var newDirection = Vector2D.clone(newTarget).sub(newSource);
+	var targetAngle = Math.atan2(newDirection.x, newDirection.y);
+	warn("new");
+	warn(currentAngle);
+	warn(targetAngle);
+
+	// check where we need to turn to (between -Pi and Pi)
+	var posMod = function(a, n){ return a - Math.floor(a/n) * n; };
+	var relativeTurn = posMod(targetAngle - currentAngle + Math.PI, 2 * Math.PI) - Math.PI;
+	// if we can't turn the full length, turn the maximum we can
+	warn(relativeTurn);
+	if (Math.abs(relativeTurn) > maxTurnAngle)
+	{
+		if (relativeTurn < 0)
+			cmpPosition.TurnTo(currentAngle - maxTurnAngle);
+		else
+			cmpPosition.TurnTo(currentAngle + maxTurnAngle);
+		return;
+	}
+	// we can take the full turn: finish the turn and continue in a straight line
+	cmpPosition.TurnTo(targetAngle);
+	this.state = "FOLLOW_LINE";
+	var usedTime = Math.abs(relativeTurn) / maxTurnAngle * turnLength;
+	this.pathIdx--;
+	this.FollowLine(turnLength - usedTime);
+};
+
+Engine.RegisterComponentType(IID_UnitMotion, "UnitMotionFormation", UnitMotionFormation);
Index: binaries/data/mods/public/simulation/templates/template_formation.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_formation.xml	(revision 16780)
+++ binaries/data/mods/public/simulation/templates/template_formation.xml	(working copy)
@@ -1,10 +1,13 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Entity>
-  <!--
+  <!---->
   <VisualActor>
     <Actor>props/special/common/waypoint_flag.xml</Actor>
+    <SilhouetteDisplay>false</SilhouetteDisplay>
+    <SilhouetteOccluder>true</SilhouetteOccluder>
+    <VisibleInAtlasOnly>false</VisibleInAtlasOnly>
   </VisualActor>
-  -->
+  <!---->
   <Formation>
     <RequiredMemberCount>1</RequiredMemberCount>
     <DisabledTooltip></DisabledTooltip>
@@ -21,6 +24,16 @@
   <FormationAttack>
     <CanAttackAsFormation>false</CanAttackAsFormation>
   </FormationAttack>
+  <Obstruction>
+    <Active>false</Active>
+    <BlockMovement>false</BlockMovement>
+    <BlockPathfinding>false</BlockPathfinding>
+    <BlockFoundation>false</BlockFoundation>
+    <BlockConstruction>false</BlockConstruction>
+    <DisableBlockMovement>true</DisableBlockMovement>
+    <DisableBlockPathfinding>true</DisableBlockPathfinding>
+	<Static width="30.0" depth="30.0"/>
+  </Obstruction>
   <Ownership/>
   <Position>
     <Altitude>0</Altitude>
@@ -35,11 +48,7 @@
     <FormationController>true</FormationController>
     <CanGuard>true</CanGuard>
   </UnitAI>
-  <UnitMotion>
-    <FormationController>true</FormationController>
-    <WalkSpeed>1.0</WalkSpeed>
-    <PassabilityClass>siege-large</PassabilityClass>
-  </UnitMotion>
+  <UnitMotionFormation/>
   <Trader>
     <GainMultiplier>1.0</GainMultiplier>
   </Trader>
Index: binaries/data/mods/public/simulation/templates/template_unit.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit.xml	(revision 16780)
+++ binaries/data/mods/public/simulation/templates/template_unit.xml	(working copy)
@@ -37,9 +37,16 @@
   <Identity>
     <GenericName>Unit</GenericName>
     <Classes datatype="tokens">Unit ConquestCritical</Classes>
-		<Formations datatype="tokens">
-			formations/null
-		</Formations>
+    <Formations datatype="tokens">
+      formations/null
+      formations/box
+      formations/column_closed
+      formations/line_closed
+      formations/column_open
+      formations/line_open
+      formations/flank
+      formations/battle_line 
+    </Formations>
   </Identity>
   <Looter/>
   <Minimap>
Index: source/scriptinterface/ScriptConversions.cpp
===================================================================
--- source/scriptinterface/ScriptConversions.cpp	(revision 16780)
+++ source/scriptinterface/ScriptConversions.cpp	(working copy)
@@ -20,6 +20,7 @@
 #include "ScriptInterface.h"
 
 #include "graphics/Entity.h"
+#include "simulation2/helpers/PathGoal.h" // to convert path goals
 #include "ps/utf16string.h"
 #include "ps/CLogger.h"
 #include "ps/CStr.h"
@@ -205,7 +206,7 @@
 	JS::RootedValue position(cx);
 	JS::RootedValue rotation(cx);
 
-	// TODO: Report type errors
+	// TODO: Report type errors when loading attributes
 	if (!JS_GetProperty(cx, obj, "player", &player) || !FromJSVal(cx, player, out.playerID))
 		FAIL("Failed to read Entity.player property");
 	if (!JS_GetProperty(cx, obj, "templateName", &templateName) || !FromJSVal(cx, templateName, out.templateName))
@@ -220,6 +221,58 @@
 	return true;
 }
 
+// TODO this doesn't work (probably always uses POINT)
+template<> bool ScriptInterface::FromJSVal<PathGoal::Type>(JSContext* cx, JS::HandleValue v, PathGoal::Type& out)
+{
+	std::string strVal;
+	ScriptInterface::FromJSVal(cx, v, static_cast<std::string&>(strVal));
+	if (strVal == "CIRCLE")
+		out = PathGoal::CIRCLE;
+	if (strVal == "INVERTED_CIRCLE")
+		out = PathGoal::INVERTED_CIRCLE;
+	else if (strVal == "SQUARE")
+		out = PathGoal::SQUARE;
+	else if (strVal == "INVERTED_SQUARE")
+		out = PathGoal::INVERTED_SQUARE;
+	else
+		out = PathGoal::POINT;
+	return true;
+}
+
+template<> bool ScriptInterface::FromJSVal<PathGoal>(JSContext* cx, JS::HandleValue val, PathGoal& out)
+{
+	JSAutoRequest rq(cx);
+	if (!val.isObject())
+		FAIL("Argument must be an object");
+
+	JS::RootedObject obj(cx, &val.toObject());
+	JS::RootedValue type(cx);
+	JS::RootedValue x(cx);
+	JS::RootedValue z(cx);
+	JS::RootedValue u(cx);
+	JS::RootedValue v(cx);
+	JS::RootedValue hw(cx);
+	JS::RootedValue hh(cx);
+
+	
+	// TODO: Report type errors
+	if (!JS_GetProperty(cx, obj, "type", &type) || !FromJSVal(cx, type, out.type))
+		FAIL("Failed to read goal.type property");
+	if (!JS_GetProperty(cx, obj, "x", &x) || !FromJSVal(cx, x, out.x))
+		FAIL("Failed to read goal.x property");
+	if (!JS_GetProperty(cx, obj, "z", &z) || !FromJSVal(cx, z, out.z))
+		FAIL("Failed to read goal.z property");
+	if (!JS_GetProperty(cx, obj, "u", &u) || !FromJSVal(cx, u, out.u))
+		FAIL("Failed to read goal.u property");
+	if (!JS_GetProperty(cx, obj, "v", &v) || !FromJSVal(cx, v, out.v))
+		FAIL("Failed to read goal.v property");
+	if (!JS_GetProperty(cx, obj, "hw", &hw) || !FromJSVal(cx, hw, out.hw))
+		FAIL("Failed to read goal.hw property");
+	if (!JS_GetProperty(cx, obj, "hh", &hh) || !FromJSVal(cx, hh, out.hh))
+		FAIL("Failed to read goal.hh property");
+	return true;
+}
+
 ////////////////////////////////////////////////////////////////
 // Primitive types:
 
Index: source/simulation2/components/CCmpObstruction.cpp
===================================================================
--- source/simulation2/components/CCmpObstruction.cpp	(revision 16780)
+++ source/simulation2/components/CCmpObstruction.cpp	(working copy)
@@ -515,6 +515,14 @@
 			m_Clearance = clearance;
 	}
 
+	virtual void SetSize(entity_pos_t width, entity_pos_t height)
+	{
+		// require this to be inactive, so we don't have to force grid updates
+		ENSURE(m_Active == false);
+		m_Size0 = width;
+		m_Size0 = height;
+	}
+
 	virtual bool IsControlPersistent()
 	{
 		return m_ControlPersist;
Index: source/simulation2/components/CCmpPathfinder.cpp
===================================================================
--- source/simulation2/components/CCmpPathfinder.cpp	(revision 16780)
+++ source/simulation2/components/CCmpPathfinder.cpp	(working copy)
@@ -184,7 +184,7 @@
 }
 
 
-pass_class_t CCmpPathfinder::GetPassabilityClass(const std::string& name)
+pass_class_t CCmpPathfinder::GetPassabilityClass(std::string name)
 {
 	if (m_PassClassMasks.find(name) == m_PassClassMasks.end())
 	{
@@ -610,7 +610,7 @@
 
 // Async path requests:
 
-u32 CCmpPathfinder::ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, entity_id_t notify)
+u32 CCmpPathfinder::ComputePathAsync(entity_pos_t x0, entity_pos_t z0, PathGoal goal, pass_class_t passClass, entity_id_t notify)
 {
 	AsyncLongPathRequest req = { m_NextAsyncTicket++, x0, z0, goal, passClass, notify };
 	m_AsyncLongPathRequests.push_back(req);
@@ -804,3 +804,38 @@
 	return ICmpObstruction::FOUNDATION_CHECK_SUCCESS;
 }
 
+u32 CCmpPathfinder::GetObstructedTiles(CFixedVector2D offset, entity_id_t id, pass_class_t passClass)
+{
+	// Test against terrain:
+	ICmpObstructionManager::ObstructionSquare square;
+	CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), id);
+	if (!cmpObstruction || !cmpObstruction->GetObstructionSquare(square))
+		return 0;
+
+	square.x += offset.X;
+	square.z += offset.Y;
+
+	UpdateGrid();
+
+	entity_pos_t expand;
+	const PathfinderPassability* passability = GetPassabilityFromMask(passClass);
+	if (passability && passability->m_HasClearance)
+		expand = passability->m_Clearance;
+
+	SimRasterize::Spans spans;
+	SimRasterize::RasterizeRectWithClearance(spans, square, expand, Pathfinding::NAVCELL_SIZE);
+	u32 obstructedTiles = 0;
+	for (SimRasterize::Span& span : spans)
+	{
+		i16 i0 = span.i0;
+		i16 i1 = span.i1;
+		i16 j = span.j;
+
+		// Fail if any span includes an impassable tile
+		for (i16 i = i0; i < i1; ++i)
+			if (i < 0 || i > m_Grid->m_W || j < 0 || j > m_Grid->m_H || !IS_PASSABLE(m_Grid->get(i, j), passClass))
+				++obstructedTiles;
+	}
+
+	return obstructedTiles;
+}
Index: source/simulation2/components/CCmpPathfinder_Common.h
===================================================================
--- source/simulation2/components/CCmpPathfinder_Common.h	(revision 16780)
+++ source/simulation2/components/CCmpPathfinder_Common.h	(working copy)
@@ -137,7 +137,7 @@
 
 	virtual void HandleMessage(const CMessage& msg, bool global);
 
-	virtual pass_class_t GetPassabilityClass(const std::string& name);
+	virtual pass_class_t GetPassabilityClass(std::string name);
 
 	virtual std::map<std::string, pass_class_t> GetPassabilityClasses();
 
@@ -177,7 +177,7 @@
 		m_LongPathfinder.ComputePath(x0, z0, goal, passClass, ret);
 	}
 
-	virtual u32 ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, entity_id_t notify);
+	virtual u32 ComputePathAsync(entity_pos_t x0, entity_pos_t z0, PathGoal goal, pass_class_t passClass, entity_id_t notify);
 
 	virtual void ComputeShortPath(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const PathGoal& goal, pass_class_t passClass, WaypointPath& ret);
 
@@ -212,6 +212,8 @@
 
 	virtual ICmpObstruction::EFoundationCheck CheckBuildingPlacement(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, entity_pos_t h, entity_id_t id, pass_class_t passClass, bool onlyCenterPoint);
 
+	virtual u32 GetObstructedTiles(CFixedVector2D offset, entity_id_t id, pass_class_t passClass);
+
 	virtual void FinishAsyncRequests();
 
 	void ProcessLongRequests(const std::vector<AsyncLongPathRequest>& longRequests);
Index: source/simulation2/components/ICmpObstruction.cpp
===================================================================
--- source/simulation2/components/ICmpObstruction.cpp	(revision 16780)
+++ source/simulation2/components/ICmpObstruction.cpp	(working copy)
@@ -47,6 +47,7 @@
 
 BEGIN_INTERFACE_WRAPPER(Obstruction)
 DEFINE_INTERFACE_METHOD_0("GetUnitRadius", entity_pos_t, ICmpObstruction, GetUnitRadius)
+DEFINE_INTERFACE_METHOD_2("SetSize", void, ICmpObstruction, SetSize, entity_pos_t, entity_pos_t)
 DEFINE_INTERFACE_METHOD_2("CheckFoundation", std::string, ICmpObstruction, CheckFoundation_wrapper, std::string, bool)
 DEFINE_INTERFACE_METHOD_0("CheckDuplicateFoundation", bool, ICmpObstruction, CheckDuplicateFoundation)
 DEFINE_INTERFACE_METHOD_2("GetEntityCollisions", std::vector<entity_id_t>, ICmpObstruction, GetEntityCollisions, bool, bool)
Index: source/simulation2/components/ICmpObstruction.h
===================================================================
--- source/simulation2/components/ICmpObstruction.h	(revision 16780)
+++ source/simulation2/components/ICmpObstruction.h	(working copy)
@@ -54,6 +54,12 @@
 
 	virtual entity_pos_t GetSize() = 0;
 
+	/**
+	 * Passively set the size of this obstruction
+	 * This must only be used on inactive obstructions, as it won't update any grids
+	 */
+	virtual void SetSize(entity_pos_t width, entity_pos_t height) = 0;
+
 	virtual entity_pos_t GetUnitRadius() = 0;
 
 	virtual void SetUnitClearance(const entity_pos_t& clearance) = 0;
Index: source/simulation2/components/ICmpPathfinder.cpp
===================================================================
--- source/simulation2/components/ICmpPathfinder.cpp	(revision 16780)
+++ source/simulation2/components/ICmpPathfinder.cpp	(working copy)
@@ -24,4 +24,7 @@
 BEGIN_INTERFACE_WRAPPER(Pathfinder)
 DEFINE_INTERFACE_METHOD_1("SetDebugOverlay", void, ICmpPathfinder, SetDebugOverlay, bool)
 DEFINE_INTERFACE_METHOD_1("SetHierDebugOverlay", void, ICmpPathfinder, SetHierDebugOverlay, bool)
+DEFINE_INTERFACE_METHOD_1("GetPassabilityClass", pass_class_t, ICmpPathfinder, GetPassabilityClass, std::string)
+DEFINE_INTERFACE_METHOD_3("GetObstructedTiles", u32, ICmpPathfinder, GetObstructedTiles, CFixedVector2D, entity_id_t, pass_class_t)
+DEFINE_INTERFACE_METHOD_5("ComputePathAsync", u32, ICmpPathfinder, ComputePathAsync, entity_pos_t, entity_pos_t, PathGoal, pass_class_t, entity_id_t)
 END_INTERFACE_WRAPPER(Pathfinder)
Index: source/simulation2/components/ICmpPathfinder.h
===================================================================
--- source/simulation2/components/ICmpPathfinder.h	(revision 16780)
+++ source/simulation2/components/ICmpPathfinder.h	(working copy)
@@ -59,7 +59,7 @@
 	 * Get the tag for a given passability class name.
 	 * Logs an error and returns something acceptable if the name is unrecognised.
 	 */
-	virtual pass_class_t GetPassabilityClass(const std::string& name) = 0;
+	virtual pass_class_t GetPassabilityClass(std::string name) = 0;
 
 	virtual entity_pos_t GetClearance(pass_class_t passClass) const = 0;
 
@@ -94,7 +94,7 @@
 	 * Returns a unique non-zero number, which will match the 'ticket' in the result,
 	 * so callers can recognise each individual request they make.
 	 */
-	virtual u32 ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, entity_id_t notify) = 0;
+	virtual u32 ComputePathAsync(entity_pos_t x0, entity_pos_t z0, PathGoal goal, pass_class_t passClass, entity_id_t notify) = 0;
 
 	/**
 	 * If the debug overlay is enabled, render the path that will computed by ComputePath.
@@ -150,6 +150,11 @@
 	 */
 	virtual ICmpObstruction::EFoundationCheck CheckBuildingPlacement(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, entity_pos_t h, entity_id_t id, pass_class_t passClass, bool onlyCenterPoint) = 0;
 
+	/**
+	 * Get the number of obstructed tiles that are under an obstruction of a
+	 * certain entity. Allow an offset to easily test alternative positions.
+	 */
+	virtual u32 GetObstructedTiles(CFixedVector2D offset, entity_id_t id, pass_class_t passClass) = 0;
 
 	/**
 	 * Toggle the storage and rendering of debug info.
Index: source/simulation2/scripting/EngineScriptConversions.cpp
===================================================================
--- source/simulation2/scripting/EngineScriptConversions.cpp	(revision 16780)
+++ source/simulation2/scripting/EngineScriptConversions.cpp	(working copy)
@@ -27,6 +27,7 @@
 #include "ps/Shapes.h"
 #include "ps/utf16string.h"
 #include "simulation2/helpers/Grid.h"
+#include "simulation2/helpers/Pathfinding.h"
 #include "simulation2/system/IComponent.h"
 #include "simulation2/system/ParamNode.h"
 
@@ -247,6 +248,31 @@
 	ret.setObject(*obj);
 }
 
+template<> void ScriptInterface::ToJSVal<Waypoint>(JSContext* cx, JS::MutableHandleValue ret, const Waypoint& val)
+{
+	JSAutoRequest rq(cx);
+
+	// apply the Vector2D prototype to the return value
+ 	ScriptInterface::CxPrivate* pCxPrivate = ScriptInterface::GetScriptInterfaceAndCBData(cx);
+	JS::RootedObject proto(cx, &pCxPrivate->pScriptInterface->GetCachedValue(ScriptInterface::CACHE_VECTOR2DPROTO).toObject());
+	JS::RootedObject obj(cx, JS_NewObject(cx, nullptr, proto, JS::NullPtr()));
+	if (!obj)
+	{
+		ret.setUndefined();
+		return;
+	}
+
+	JS::RootedValue x(cx);
+	JS::RootedValue y(cx);
+	ToJSVal(cx, &x, val.x);
+	ToJSVal(cx, &y, val.z);
+
+	JS_SetProperty(cx, obj, "x", x);
+	JS_SetProperty(cx, obj, "y", y);
+
+	ret.setObject(*obj);
+}
+
 template<> void ScriptInterface::ToJSVal<Grid<u8> >(JSContext* cx, JS::MutableHandleValue ret, const Grid<u8>& val)
 {
 	JSAutoRequest rq(cx);
@@ -309,3 +335,22 @@
 	}
 	ret.setObject(*obj);
 }
+
+template<> void ScriptInterface::ToJSVal<WaypointPath>(JSContext* cx, JS::MutableHandleValue ret, const WaypointPath& val)
+{
+	JSAutoRequest rq(cx);
+	JS::RootedObject obj(cx, JS_NewArrayObject(cx, 0));
+	if (!obj)
+	{
+		ret.setUndefined();
+		return;
+	}
+	const std::vector<Waypoint>& waypointList = val.m_Waypoints;
+	for (size_t i = 0; i < waypointList.size(); ++i)
+	{
+		JS::RootedValue el(cx);
+		ScriptInterface::ToJSVal<Waypoint>(cx, &el, waypointList[i]);
+		JS_SetElement(cx, obj, i, el);
+	}
+	ret.setObject(*obj);
+}
Index: source/simulation2/scripting/MessageTypeConversions.cpp
===================================================================
--- source/simulation2/scripting/MessageTypeConversions.cpp	(revision 16780)
+++ source/simulation2/scripting/MessageTypeConversions.cpp	(working copy)
@@ -385,10 +385,12 @@
 
 ////////////////////////////////
 
-JS::Value CMessagePathResult::ToJSVal(ScriptInterface& UNUSED(scriptInterface)) const
+JS::Value CMessagePathResult::ToJSVal(ScriptInterface& scriptInterface) const
 {
-	LOGWARNING("CMessagePathResult::ToJSVal not implemented");
-	return JS::UndefinedValue();
+	TOJSVAL_SETUP();
+	SET_MSG_PROPERTY(ticket);
+	SET_MSG_PROPERTY(path);
+	return JS::ObjectValue(*obj);
 }
 
 CMessage* CMessagePathResult::FromJSVal(ScriptInterface& UNUSED(scriptInterface), JS::HandleValue UNUSED(val))
