Index: binaries/data/mods/public/simulation/components/Timer.js
===================================================================
--- binaries/data/mods/public/simulation/components/Timer.js	(revision 14330)
+++ binaries/data/mods/public/simulation/components/Timer.js	(working copy)
@@ -16,9 +16,17 @@
 Timer.prototype.GetTime = function()
 {
 	return this.time;
-}
+};
 
 /**
+ * Returns the turn length of the latest turn in millisecons
+ */
+Timer.prototype.GetTurnLength = function()
+{
+	return this.turnLength;
+};
+
+/**
  * Create a new timer, which will call the 'funcname' method with arguments (data, lateness)
  * on the 'iid' component of the 'ent' entity, after at least 'time' milliseconds.
  * 'lateness' is how late the timer is executed after the specified time (in milliseconds).
@@ -60,8 +68,8 @@
 
 Timer.prototype.OnUpdate = function(msg)
 {
-	var dt = Math.round(msg.turnLength * 1000);
-	this.time += dt;
+	this.turnLength = Math.round(msg.turnLength * 1000);
+	this.time += this.turnLength;
 
 	// Collect the timers that need to run
 	// (We do this in two stages to avoid deleting from the timer list while
Index: binaries/data/mods/public/simulation/components/UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/UnitAI.js	(revision 14330)
+++ binaries/data/mods/public/simulation/components/UnitAI.js	(working copy)
@@ -171,6 +171,10 @@
 		// ignore
 	},
 
+	"Update": function(msg) {
+		// ignore
+	},
+
 	// Formation handlers:
 
 	"FormationLeave": function(msg) {
@@ -378,7 +382,7 @@
 		// We use the distance between the entities to account for ranged attacks
 		var distance = DistanceBetweenEntities(this.entity, this.order.data.target) + (+this.template.FleeDistance);
 		var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
-		if (cmpUnitMotion.MoveToTargetRange(this.order.data.target, distance, -1))
+		if (cmpUnitMotion.MoveToTargetRange(this.order.data.target, distance, -1, 0))
 		{
 			// We've started fleeing from the given target
 			if (this.IsAnimal())
@@ -413,7 +417,7 @@
 		this.order.data.attackType = type;
 
 		// If we are already at the target, try attacking it from here
-		if (this.CheckTargetAttackRange(this.order.data.target, IID_Attack, this.order.data.attackType))
+		if (this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType))
 		{
 			this.StopMoving();
 			// For packable units within attack range:
@@ -485,7 +489,7 @@
 		}
 
 		// Try to move within attack range
-		if (this.MoveToTargetAttackRange(this.order.data.target, IID_Attack, this.order.data.attackType))
+		if (this.MoveToTargetAttackRange(this.order.data.target, this.order.data.attackType))
 		{
 			// We've started walking to the given point
 			if (this.IsAnimal())
@@ -1590,7 +1594,7 @@
 
 				"MoveCompleted": function() {
 
-					if (this.CheckTargetAttackRange(this.order.data.target, IID_Attack , this.order.data.attackType)) 
+					if (this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType)) 
 					{
 						// If the unit needs to unpack, do so
 						if (this.CanUnpack())
@@ -1600,7 +1604,7 @@
 					} 
 					else 
 					{
-						if (this.MoveToTargetAttackRange(this.order.data.target, IID_Attack, this.order.data.attackType))
+						if (this.MoveToTargetAttackRange(this.order.data.target, this.order.data.attackType))
 						{
 							this.SetNextState("APPROACHING");
 						}
@@ -1625,9 +1629,9 @@
 			"UNPACKING": {
 				"enter": function() {
 					// If we're not in range yet (maybe we stopped moving), move to target again
-					if (!this.CheckTargetAttackRange(this.order.data.target, IID_Attack, this.order.data.attackType))
+					if (!this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType))
 					{
-						if (this.MoveToTargetAttackRange(this.order.data.target, IID_Attack, this.order.data.attackType))
+						if (this.MoveToTargetAttackRange(this.order.data.target, this.order.data.attackType))
 							this.SetNextState("APPROACHING");
 						else
 						{
@@ -1690,55 +1694,66 @@
 				"Timer": function(msg) {
 					var target = this.order.data.target;
 					// Check the target is still alive and attackable
-					if (this.TargetIsAlive(target) && this.CanAttack(target, this.order.data.forceResponse || null))
+					if 
+					(
+						!this.TargetIsAlive(target) || 
+						!this.CanAttack(target, this.order.data.forceResponse || null) ||
+						!this.CheckTargetAttackRange(target, this.order.data.attackType)
+					)
+						return;
+
+					// If we are hunting, first update the target position of the gather order so we know where will be the killed animal
+					if (this.order.data.hunting && this.orderQueue[1] && this.orderQueue[1].data.lastPos)
 					{
-						// Check we can still reach the target
-						if (this.CheckTargetAttackRange(target, IID_Attack, this.order.data.attackType))
+						var cmpPosition = Engine.QueryInterface(this.order.data.target, IID_Position);
+						if (cmpPosition && cmpPosition.IsInWorld())
 						{
-							// If we are hunting, first update the target position of the gather order so we know where will be the killed animal
-							if (this.order.data.hunting && this.orderQueue[1] && this.orderQueue[1].data.lastPos)
-							{
-								var cmpPosition = Engine.QueryInterface(this.order.data.target, IID_Position);
-								if (cmpPosition && cmpPosition.IsInWorld())
-								{
-									// Store the initial position, so that we can find the rest of the herd later
-									if (!this.orderQueue[1].data.initPos)
-										this.orderQueue[1].data.initPos = this.orderQueue[1].data.lastPos;
-									this.orderQueue[1].data.lastPos = cmpPosition.GetPosition();
-									// We still know where the animal is, so we shouldn't give up before going there
-									this.orderQueue[1].data.secondTry = undefined;
-								}
-							}
+							// Store the initial position, so that we can find the rest of the herd later
+							if (!this.orderQueue[1].data.initPos)
+								this.orderQueue[1].data.initPos = this.orderQueue[1].data.lastPos;
+							this.orderQueue[1].data.lastPos = cmpPosition.GetPosition();
+							// We still know where the animal is, so we shouldn't give up before going there
+							this.orderQueue[1].data.secondTry = undefined;
+						}
+					}
 
-							var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
-							this.lastAttacked = cmpTimer.GetTime() - msg.lateness;
+					var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+					this.lastAttacked = cmpTimer.GetTime() - msg.lateness;
 
-							this.FaceTowardsTarget(target);
-							var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
-							cmpAttack.PerformAttack(this.order.data.attackType, target);
+					this.FaceTowardsTarget(target);
+					var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
+					cmpAttack.PerformAttack(this.order.data.attackType, target);
 
-							if (this.resyncAnimation)
-							{
-								this.SetAnimationSync(this.attackTimers.repeat, this.attackTimers.repeat);
-								this.resyncAnimation = false;
-							}
-							return;
-						}
+					if (this.resyncAnimation)
+					{
+						this.SetAnimationSync(this.attackTimers.repeat, this.attackTimers.repeat);
+						this.resyncAnimation = false;
+					}
+				},
 
-						// Can't reach it - try to chase after it
-						if (this.ShouldChaseTargetedEntity(target, this.order.data.force))
-						{
-							if (this.MoveToTargetRange(target, IID_Attack, this.order.data.attackType))
-							{
-								this.SetNextState("COMBAT.CHASING");
-								return;
-							}
-						}
+				// respond to target position changes and target deaths immediately
+				"Update": function(msg) {
+					var target = this.order.data.target;
+					var attackType = this.order.data.attackType;
+					if 
+					(
+						this.TargetIsAlive(target) &&
+						this.CanAttack(target, this.order.data.forceResponse || null)
+					)
+					{
+						if
+						(
+							!this.CheckTargetAttackRange(target, attackType) &&
+							this.ShouldChaseTargetedEntity(target, this.order.data.force) &&
+							this.MoveToTargetAttackRange(target, attackType)
+						)
+							this.SetNextState("COMBAT.CHASING");
+						return;
 					}
 
-					this.oldAttackType = this.order.data.attackType;
 					// Can't reach it, no longer owned by enemy, or it doesn't exist any more - give up
 					// Except if in WalkAndFight mode where we look for more ennemies around before moving again
+					this.oldAttackType = attackType;
 					if (this.FinishOrder())
 					{
 						if (this.orderQueue.length > 0 && this.orderQueue[0].type == "WalkAndFight")
@@ -1748,12 +1763,7 @@
 
 					// See if we can switch to a new nearby enemy
 					if (this.FindNewTargets())
-					{
-						// Attempt to immediately re-enter the timer function, to avoid wasting the attack.
-						if (this.orderQueue.length > 0 && this.orderQueue[0].data.attackType == this.oldAttackType)
-							this.TimerHandler(msg.data, msg.lateness);
 						return;
-					}
 
 					// Return to our original position
 					if (this.GetStance().respondHoldGround)
@@ -1760,9 +1770,6 @@
 						this.WalkToHeldPosition();
 				},
 
-				// TODO: respond to target deaths immediately, rather than waiting
-				// until the next Timer event
-
 				"Attacked": function(msg) {
 					if (this.order.data.target != msg.data.attacker)
 					{
@@ -3587,6 +3594,11 @@
 	UnitFsm.ProcessMessage(this, {"type": "PackFinished", "packed": msg.packed});
 };
 
+UnitAI.prototype.OnUpdate = function(msg)
+{
+	UnitFsm.ProcessMessage(this, {"type": "Update", "data": msg});
+}
+
 //// Helper functions to be called by the FSM ////
 
 UnitAI.prototype.GetWalkSpeed = function()
@@ -3856,10 +3868,10 @@
 		return false;
 
 	var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
-	return cmpUnitMotion.MoveToTargetRange(target, 0, 0);
+	return cmpUnitMotion.MoveToTargetRange(target, 0, 0, 0);
 };
 
-UnitAI.prototype.MoveToTargetRange = function(target, iid, type)
+UnitAI.prototype.MoveToTargetRange = function(target, iid, type, time)
 {
 	if (!this.CheckTargetVisible(target))
 		return false;
@@ -3868,9 +3880,11 @@
 	if (!cmpRanged)
 		return false;
 	var range = cmpRanged.GetRange(type);
+	if (!time) 
+		time = 0;
 
 	var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
-	return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
+	return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max, time);
 };
 
 /**
@@ -3878,17 +3892,22 @@
  * for melee attacks, this goes straight to the default range checks
  * for ranged attacks, the parabolic range is used
  */
-UnitAI.prototype.MoveToTargetAttackRange = function(target, iid, type)
+UnitAI.prototype.MoveToTargetAttackRange = function(target, type)
 {
+	var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
+	var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+	var timers = cmpAttack.GetTimers(type);
+	// add one turnlength, as even without prepare time, there's one turn between stopping
+	// to move and starting to attack
+	var time = timers.prepare/1000 + cmpTimer.GetTurnLength() / 1000; 
 
 	if(type!= "Ranged") 
-		return this.MoveToTargetRange(target, iid, type);
+		return this.MoveToTargetRange(target, IID_Attack, type, time);
 	
 	if (!this.CheckTargetVisible(target)) 
 		return false;
 	
-	var cmpRanged = Engine.QueryInterface(this.entity, iid);
-	var range = cmpRanged.GetRange(type);
+	var range = cmpAttack.GetRange(type);
 
 	var thisCmpPosition = Engine.QueryInterface(this.entity, IID_Position);
 	var s = thisCmpPosition.GetPosition();
@@ -3913,20 +3932,23 @@
 	var guessedMaxRange = (range.max + parabolicMaxRange)/2;
 
 	var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
-	if (cmpUnitMotion.MoveToTargetRange(target, range.min, guessedMaxRange))
+	if (cmpUnitMotion.MoveToTargetRange(target, range.min, guessedMaxRange, time))
 		return true;
 
 	// if that failed, try closer
-	return cmpUnitMotion.MoveToTargetRange(target, range.min, Math.min(range.max, parabolicMaxRange));
+	return cmpUnitMotion.MoveToTargetRange(target, range.min, Math.min(range.max, parabolicMaxRange), time);
 };
 
-UnitAI.prototype.MoveToTargetRangeExplicit = function(target, min, max)
+UnitAI.prototype.MoveToTargetRangeExplicit = function(target, min, max, time)
 {
 	if (!this.CheckTargetVisible(target))
 		return false;
 
+	if (!time)
+		time = 0;
+
 	var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
-	return cmpUnitMotion.MoveToTargetRange(target, min, max);
+	return cmpUnitMotion.MoveToTargetRange(target, min, max, time);
 };
 
 UnitAI.prototype.MoveToGarrisonRange = function(target)
@@ -3940,7 +3962,7 @@
 	var range = cmpGarrisonHolder.GetLoadingRange();
 
 	var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
-	return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
+	return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max, 0);
 };
 
 UnitAI.prototype.CheckPointRangeExplicit = function(x, z, min, max)
@@ -3966,17 +3988,17 @@
  * For ranged attacks, the parabolic formula is used to accout for bigger ranges
  * when the target is lower, and smaller ranges when the target is higher
  */ 
-UnitAI.prototype.CheckTargetAttackRange = function(target, iid, type)
+UnitAI.prototype.CheckTargetAttackRange = function(target, type)
 {
 
 	if (type != "Ranged") 
-		return this.CheckTargetRange(target,iid,type);
+		return this.CheckTargetRange(target, IID_Attack, type);
 	
 	var targetCmpPosition = Engine.QueryInterface(target, IID_Position);
 	if (!targetCmpPosition || !targetCmpPosition.IsInWorld()) 
 		return false; 
 
-	var cmpRanged = Engine.QueryInterface(this.entity, iid);
+	var cmpRanged = Engine.QueryInterface(this.entity, IID_Attack);
 	var range = cmpRanged.GetRange(type);
 
 	var thisCmpPosition = Engine.QueryInterface(this.entity, IID_Position);
@@ -4220,7 +4242,7 @@
 		if (cmpUnitAI && cmpAttack)
 		{
 			for each (var type in cmpAttack.GetAttackTypes())
-				if (cmpUnitAI.CheckTargetAttackRange(this.isGuardOf, IID_Attack, type))
+				if (cmpUnitAI.CheckTargetAttackRange(this.isGuardOf, type))
 					return false;
 		}
 	}
@@ -4267,7 +4289,7 @@
 		if (cmpUnitAI && cmpAttack)
 		{
 			for each (var type in cmpAttack.GetAttackTypes())
-				if (cmpUnitAI.CheckTargetAttackRange(this.isGuardOf, IID_Attack, type))
+				if (cmpUnitAI.CheckTargetAttackRange(this.isGuardOf, type))
 					return true;
 		}
 	}
Index: source/simulation2/components/CCmpObstruction.cpp
===================================================================
--- source/simulation2/components/CCmpObstruction.cpp	(revision 14330)
+++ source/simulation2/components/CCmpObstruction.cpp	(working copy)
@@ -442,8 +442,18 @@
 		return m_Tag;
 	}
 
+	virtual bool GetPreviousObstructionSquare(ICmpObstructionManager::ObstructionSquare& out)
+	{
+		return GetObstructionSquare(out, true);
+	}
+
 	virtual bool GetObstructionSquare(ICmpObstructionManager::ObstructionSquare& out)
 	{
+		return GetObstructionSquare(out, false);
+	}
+
+	virtual bool GetObstructionSquare(ICmpObstructionManager::ObstructionSquare& out, bool previousPosition)
+	{
 		CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
 		if (!cmpPosition)
 			return false; // error
@@ -455,7 +465,11 @@
 		if (!cmpPosition->IsInWorld())
 			return false; // no obstruction square
 
-		CFixedVector2D pos = cmpPosition->GetPosition2D();
+		CFixedVector2D pos;
+		if (previousPosition)
+			pos = cmpPosition->GetPreviousPosition2D();
+		else
+			pos = cmpPosition->GetPosition2D();
 		if (m_Type == STATIC)
 			out = cmpObstructionManager->GetStaticShapeObstruction(pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1);
 		else if (m_Type == UNIT)
Index: source/simulation2/components/CCmpUnitMotion.cpp
===================================================================
--- source/simulation2/components/CCmpUnitMotion.cpp	(revision 14330)
+++ source/simulation2/components/CCmpUnitMotion.cpp	(working copy)
@@ -107,7 +107,7 @@
 static const CColor OVERLAY_COLOUR_LONG_PATH(1, 1, 1, 1);
 static const CColor OVERLAY_COLOUR_SHORT_PATH(1, 0, 0, 1);
 
-static const entity_pos_t g_GoalDelta = entity_pos_t::FromInt(TERRAIN_TILE_SIZE)/4; // for extending the goal outwards/inwards a little bit
+static const entity_pos_t g_GoalDelta = entity_pos_t::FromInt(TERRAIN_TILE_SIZE)/2; // for extending the goal outwards/inwards a little bit
 
 class CCmpUnitMotion : public ICmpUnitMotion
 {
@@ -132,6 +132,7 @@
 	bool m_FormationController;
 	fixed m_WalkSpeed, m_OriginalWalkSpeed; // in metres per second
 	fixed m_RunSpeed, m_OriginalRunSpeed;
+	fixed m_TimeToStayInRange;
 	ICmpPathfinder::pass_class_t m_PassClass;
 	ICmpPathfinder::cost_class_t m_CostClass;
 
@@ -297,6 +298,7 @@
 		m_WalkSpeed = m_OriginalWalkSpeed = paramNode.GetChild("WalkSpeed").ToFixed();
 		m_Speed = m_WalkSpeed;
 		m_CurSpeed = fixed::Zero();
+		m_TimeToStayInRange = fixed::Zero();
 
 		if (paramNode.GetChild("Run").IsOk())
 			m_RunSpeed = m_OriginalRunSpeed = paramNode.GetChild("Run").GetChild("Speed").ToFixed();
@@ -350,6 +352,8 @@
 
 		serialize.NumberFixed_Unbounded("speed", m_Speed);
 
+		serialize.NumberFixed_Unbounded("timeToStayInRange", m_TimeToStayInRange);
+
 		serialize.Bool("moving", m_Moving);
 		serialize.Bool("facePointAfterMove", m_FacePointAfterMove);
 
@@ -470,7 +474,7 @@
 
 	virtual bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange);
 	virtual bool IsInPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange);
-	virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange);
+	virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t timeToStayInRange);
 	virtual bool IsInTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange);
 	virtual void MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z);
 
@@ -995,11 +999,21 @@
 						}
 						else
 						{
+							// check if target was reached in case of a moving target
+							CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), m_TargetEntity);
+							if 
+							(
+								cmpUnitMotion && 
+								cmpUnitMotion->IsMoving() &&
+								MoveToTargetRange(m_TargetEntity, m_TargetMinRange, m_TargetMaxRange, m_TimeToStayInRange)
+							)
+								return;
+
 							// Not in formation, so just finish moving
-
 							StopMoving();
+							m_State = STATE_IDLE;
+							MoveSucceeded();
 
-
 							if (m_FacePointAfterMove)
 								FaceTowardsPointFromPos(pos, m_FinalGoal.x, m_FinalGoal.z);
 							// TODO: if the goal was a square building, we ought to point towards the
@@ -1305,6 +1319,7 @@
 
 bool CCmpUnitMotion::MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange, entity_id_t target)
 {
+	// TODO also take moving targets into account, like in MoveToTargetRange
 	PROFILE("MoveToPointRange");
 
 	CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
@@ -1383,6 +1398,7 @@
 	m_TargetOffset = CFixedVector2D();
 	m_TargetMinRange = minRange;
 	m_TargetMaxRange = maxRange;
+	m_TimeToStayInRange = fixed::Zero();
 	m_FinalGoal = goal;
 
 	BeginPathing(pos, goal);
@@ -1454,7 +1470,7 @@
 	return (errCircle < errSquare);
 }
 
-bool CCmpUnitMotion::MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange)
+bool CCmpUnitMotion::MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t timeToStayInRange)
 {
 	PROFILE("MoveToTargetRange");
 
@@ -1474,6 +1490,10 @@
 	if (cmpObstruction)
 		hasObstruction = cmpObstruction->GetObstructionSquare(obstruction);
 
+	CmpPtr<ICmpPosition> cmpTargetPosition(GetSimContext(), target);
+	if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld())
+		return false;
+
 	/*
 	 * If we're starting outside the maxRange, we need to move closer in.
 	 * If we're starting inside the minRange, we need to move further out.
@@ -1501,6 +1521,33 @@
 
 	if (hasObstruction)
 	{
+		// if the target is moving, take an overshoot into account
+		entity_pos_t overShoot = fixed::Zero();
+		CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), target);
+		if (cmpUnitMotion)
+			overShoot = cmpUnitMotion->GetCurrentSpeed().Multiply(timeToStayInRange);
+
+		// get the rotation difference, and bring it to the (-pi,pi] interval
+		fixed rotationDifference = cmpPosition->GetRotation().Y - cmpTargetPosition->GetRotation().Y;
+		fixed Pi = fixed::Pi();
+		while (rotationDifference > Pi)
+			rotationDifference -= Pi * 2;
+		while (rotationDifference <= -Pi)
+			rotationDifference += Pi * 2;
+
+		// take the rotation into account to decide which range to change
+		entity_pos_t newMaxRange = maxRange;
+		entity_pos_t newMinRange = minRange;
+		if (rotationDifference < Pi / 2 && rotationDifference > -Pi / 2)
+			newMaxRange -= overShoot;
+		else
+			newMinRange += overShoot;
+		// make sure the ranges are possible, and have the same behaviour 
+		if (newMaxRange <= minRange && maxRange >= minRange)
+			newMaxRange = minRange;
+		if (maxRange > fixed::Zero() && newMinRange > newMaxRange)
+			newMinRange = newMaxRange;
+
 		CFixedVector2D halfSize(obstruction.hw, obstruction.hh);
 		ICmpPathfinder::Goal goal;
 		goal.x = obstruction.x;
@@ -1508,13 +1555,18 @@
 
 		entity_pos_t distance = Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize);
 
-		if (distance < minRange)
+		// compare with previous obstruction
+		ICmpObstructionManager::ObstructionSquare previousObstruction;
+		cmpObstruction->GetPreviousObstructionSquare(previousObstruction);
+		entity_pos_t previousDistance = Geometry::DistanceToSquare(pos - CFixedVector2D(previousObstruction.x, previousObstruction.z), obstruction.u, obstruction.v, halfSize);
+
+		if (distance < newMinRange && previousDistance < newMinRange)
 		{
 			// Too close to the square - need to move away
 
 			// TODO: maybe we should do the ShouldTreatTargetAsCircle thing here?
 
-			entity_pos_t goalDistance = minRange + g_GoalDelta;
+			entity_pos_t goalDistance = newMinRange + g_GoalDelta;
 
 			goal.type = ICmpPathfinder::Goal::SQUARE;
 			goal.u = obstruction.u;
@@ -1523,7 +1575,7 @@
 			goal.hw = obstruction.hw + delta;
 			goal.hh = obstruction.hh + delta;
 		}
-		else if (maxRange < entity_pos_t::Zero() || distance < maxRange)
+		else if (newMaxRange < entity_pos_t::Zero() || distance < newMaxRange || previousDistance < newMaxRange)
 		{
 			// We're already in range - no need to move anywhere
 			if (m_FacePointAfterMove)
@@ -1537,7 +1589,7 @@
 			// Circumscribe the square
 			entity_pos_t circleRadius = halfSize.Length();
 
-			if (ShouldTreatTargetAsCircle(maxRange, obstruction.hw, obstruction.hh, circleRadius))
+			if (ShouldTreatTargetAsCircle(newMaxRange, obstruction.hw, obstruction.hh, circleRadius))
 			{
 				// The target is small relative to our range, so pretend it's a circle
 
@@ -1546,7 +1598,7 @@
 				// check is still valid (though not sufficient)
 				entity_pos_t circleDistance = (pos - CFixedVector2D(obstruction.x, obstruction.z)).Length() - circleRadius;
 
-				if (circleDistance < maxRange)
+				if (circleDistance < newMaxRange)
 				{
 					// We're already in range - no need to move anywhere
 					if (m_FacePointAfterMove)
@@ -1554,8 +1606,19 @@
 					return false;
 				}
 
-				entity_pos_t goalDistance = maxRange - g_GoalDelta;
+				entity_pos_t previousCircleDistance = (pos - CFixedVector2D(previousObstruction.x, previousObstruction.z)).Length() - circleRadius;
 
+				if (previousCircleDistance < newMaxRange)
+				{
+					// We're already in range - no need to move anywhere
+					if (m_FacePointAfterMove)
+						FaceTowardsPointFromPos(pos, goal.x, goal.z);
+					return false;
+				}
+
+
+				entity_pos_t goalDistance = newMaxRange - g_GoalDelta;
+
 				goal.type = ICmpPathfinder::Goal::CIRCLE;
 				goal.hw = circleRadius + goalDistance;
 			}
@@ -1564,7 +1627,7 @@
 				// The target is large relative to our range, so treat it as a square and
 				// get close enough that the diagonals come within range
 
-				entity_pos_t goalDistance = (maxRange - g_GoalDelta)*2 / 3; // multiply by slightly less than 1/sqrt(2)
+				entity_pos_t goalDistance = (newMaxRange - g_GoalDelta)*2 / 3; // multiply by slightly less than 1/sqrt(2)
 
 				goal.type = ICmpPathfinder::Goal::SQUARE;
 				goal.u = obstruction.u;
@@ -1580,6 +1643,7 @@
 		m_TargetOffset = CFixedVector2D();
 		m_TargetMinRange = minRange;
 		m_TargetMaxRange = maxRange;
+		m_TimeToStayInRange = timeToStayInRange;
 		m_FinalGoal = goal;
 
 		BeginPathing(pos, goal);
@@ -1590,10 +1654,6 @@
 	{
 		// The target didn't have an obstruction or obstruction shape, so treat it as a point instead
 
-		CmpPtr<ICmpPosition> cmpTargetPosition(GetSimContext(), target);
-		if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld())
-			return false;
-
 		CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D();
 
 		return MoveToPointRange(targetPos.X, targetPos.Y, minRange, maxRange, target);
@@ -1626,12 +1686,17 @@
 		CFixedVector2D halfSize(obstruction.hw, obstruction.hh);
 		entity_pos_t distance = Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize);
 
+		// compare with previous obstruction
+		ICmpObstructionManager::ObstructionSquare previousObstruction;
+		cmpObstruction->GetPreviousObstructionSquare(previousObstruction);
+		entity_pos_t previousDistance = Geometry::DistanceToSquare(pos - CFixedVector2D(previousObstruction.x, previousObstruction.z), obstruction.u, obstruction.v, halfSize);
+		
 		// See if we're too close to the target square
-		if (distance < minRange)
+		if (distance < minRange && previousDistance < minRange)
 			return false;
 
 		// See if we're close enough to the target square
-		if (maxRange < entity_pos_t::Zero() || distance <= maxRange)
+		if (maxRange < entity_pos_t::Zero() || distance <= maxRange || previousDistance <= maxRange)
 			return true;
 
 		entity_pos_t circleRadius = halfSize.Length();
@@ -1645,6 +1710,11 @@
 
 			if (circleDistance <= maxRange)
 				return true;
+			// also check circle around previous position
+			circleDistance = (pos - CFixedVector2D(previousObstruction.x, previousObstruction.z)).Length() - circleRadius;
+
+			if (circleDistance <= maxRange)
+				return true;
 		}
 
 		return false;
@@ -1655,14 +1725,12 @@
 		if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld())
 			return false;
 
-		CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D();
+		CFixedVector2D targetPos = cmpTargetPosition->GetPreviousPosition2D();
 
 		entity_pos_t distance = (pos - targetPos).Length();
 
-		if (minRange <= distance && (maxRange < entity_pos_t::Zero() || distance <= maxRange))
-			return true;
-
-		return false;
+		return minRange <= distance && 
+			(maxRange < entity_pos_t::Zero() || distance <= maxRange);
 	}
 }
 
Index: source/simulation2/components/ICmpObstruction.h
===================================================================
--- source/simulation2/components/ICmpObstruction.h	(revision 14330)
+++ source/simulation2/components/ICmpObstruction.h	(working copy)
@@ -47,6 +47,11 @@
 	 */
 	virtual bool GetObstructionSquare(ICmpObstructionManager::ObstructionSquare& out) = 0;
 
+	/**
+	 * Same as the method above, but returns an obstruction shape for the previous turn
+	 */
+	virtual bool GetPreviousObstructionSquare(ICmpObstructionManager::ObstructionSquare& out) = 0;
+
 	virtual entity_pos_t GetUnitRadius() = 0;
 
 	virtual bool IsControlPersistent() = 0;
Index: source/simulation2/components/ICmpUnitMotion.cpp
===================================================================
--- source/simulation2/components/ICmpUnitMotion.cpp	(revision 14330)
+++ source/simulation2/components/ICmpUnitMotion.cpp	(working copy)
@@ -26,7 +26,7 @@
 DEFINE_INTERFACE_METHOD_4("MoveToPointRange", bool, ICmpUnitMotion, MoveToPointRange, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t)
 DEFINE_INTERFACE_METHOD_4("IsInPointRange", bool, ICmpUnitMotion, IsInPointRange, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t)
 DEFINE_INTERFACE_METHOD_3("IsInTargetRange", bool, ICmpUnitMotion, IsInTargetRange, entity_id_t, entity_pos_t, entity_pos_t)
-DEFINE_INTERFACE_METHOD_3("MoveToTargetRange", bool, ICmpUnitMotion, MoveToTargetRange, entity_id_t, entity_pos_t, entity_pos_t)
+DEFINE_INTERFACE_METHOD_4("MoveToTargetRange", bool, ICmpUnitMotion, MoveToTargetRange, entity_id_t, entity_pos_t, entity_pos_t, entity_pos_t)
 DEFINE_INTERFACE_METHOD_3("MoveToFormationOffset", void, ICmpUnitMotion, MoveToFormationOffset, entity_id_t, entity_pos_t, entity_pos_t)
 DEFINE_INTERFACE_METHOD_2("FaceTowardsPoint", void, ICmpUnitMotion, FaceTowardsPoint, entity_pos_t, entity_pos_t)
 DEFINE_INTERFACE_METHOD_0("StopMoving", void, ICmpUnitMotion, StopMoving)
@@ -60,9 +60,9 @@
 		return m_Script.Call<bool>("IsInTargetRange", target, minRange, maxRange);
 	}
 
-	virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange)
+	virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t timeToStayInRange)
 	{
-		return m_Script.Call<bool>("MoveToTargetRange", target, minRange, maxRange);
+		return m_Script.Call<bool>("MoveToTargetRange", target, minRange, maxRange, timeToStayInRange);
 	}
 
 	virtual void MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z)
Index: source/simulation2/components/ICmpUnitMotion.h
===================================================================
--- source/simulation2/components/ICmpUnitMotion.h	(revision 14330)
+++ source/simulation2/components/ICmpUnitMotion.h	(working copy)
@@ -66,7 +66,7 @@
 	 * and sends another MotionChanged after finishing moving.
 	 * If maxRange is negative, then the maximum range is treated as infinity.
 	 */
-	virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) = 0;
+	virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t timeToStayInRange) = 0;
 
 	/**
 	 * Join a formation, and move towards a given offset relative to the formation controller entity.
