diff --git binaries/data/mods/public/gui/session/selection.js binaries/data/mods/public/gui/session/selection.js
index c678873..fd19fd6 100644
--- binaries/data/mods/public/gui/session/selection.js
+++ binaries/data/mods/public/gui/session/selection.js
@@ -235,8 +235,10 @@ EntitySelection.prototype.getTemplateNames = function()
  */
 EntitySelection.prototype.update = function()
 {
-	var changed = false;
 	this.checkRenamedEntities();
+
+	var miraged = {};
+	var changed = false;
 	for each (var ent in this.selected)
 	{
 		var entState = GetEntityState(ent);
@@ -250,6 +252,13 @@ EntitySelection.prototype.update = function()
 			continue;
 		}
 
+		// Manually replace newly miraged entities by their mirages
+		if (entState.fogging && entState.fogging.mirage)
+		{
+			miraged[ent] = entState.fogging.mirage;
+			continue;
+		}
+
 		// Remove non-visible units (e.g. moved back into fog-of-war)
 		if (entState.visibility == "hidden")
 		{
@@ -264,6 +273,9 @@ EntitySelection.prototype.update = function()
 			continue;
 		}
 	}
+
+	this.rebuildSelection(miraged);
+
 	if (changed)
 		this.onChange();
 };
diff --git binaries/data/mods/public/gui/session/selection_details.js binaries/data/mods/public/gui/session/selection_details.js
index 4d8a347..baa4d90 100644
--- binaries/data/mods/public/gui/session/selection_details.js
+++ binaries/data/mods/public/gui/session/selection_details.js
@@ -164,7 +164,7 @@ function displaySingle(entState, template)
 		Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = sprintf(translate("Gain: %(amount)s"), { amount: getTradingTooltip(entState.trader.goods.amount) });
 	}
 	// And for number of workers
-	else if (entState.foundation)
+	else if (entState.foundation && !entState.mirage)
 	{
 		Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
 		Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
@@ -172,7 +172,7 @@ function displaySingle(entState, template)
 		Engine.GetGUIObjectByName("resourceCarryingText").caption = entState.foundation.numBuilders + "    ";
 		Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = sprintf(translate("Number of builders.\nTasking another to this foundation would speed construction up by %(numb)s%%"), { numb : Math.round((Math.pow((entState.foundation.numBuilders+1)/entState.foundation.numBuilders, 0.7) - 1.0)*100) });
 	}
-	else if (entState.resourceSupply && (!entState.resourceSupply.killBeforeGather || !entState.hitpoints))
+	else if (entState.resourceSupply && (!entState.resourceSupply.killBeforeGather || !entState.hitpoints) && !entState.mirage)
 	{
 		Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
 		Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
diff --git binaries/data/mods/public/gui/session/unit_actions.js binaries/data/mods/public/gui/session/unit_actions.js
index 4c14055..1dab22c 100644
--- binaries/data/mods/public/gui/session/unit_actions.js
+++ binaries/data/mods/public/gui/session/unit_actions.js
@@ -656,6 +656,12 @@ var g_EntityCommands =
 	"delete": {
 		"getInfo": function(entState)
 		{
+			if (entState.mirage)
+				return {
+					"tooltip": translate("You cannot destroy this entity because it is in the fog-of-war"),
+					"icon": "kill_small.png"
+				};
+
 			return {
 				"tooltip": translate("Delete"),
 				"icon": "kill_small.png"
@@ -663,6 +669,9 @@ var g_EntityCommands =
 		},
 		"execute": function(entState)
 		{
+			if (entState.mirage)
+				return;
+
 			var selection = g_Selection.toList();
 			if (selection.length < 1)
 				return;
diff --git binaries/data/mods/public/simulation/components/BuildingAI.js binaries/data/mods/public/simulation/components/BuildingAI.js
index 4eef15b..ef124d9 100644
--- binaries/data/mods/public/simulation/components/BuildingAI.js
+++ binaries/data/mods/public/simulation/components/BuildingAI.js
@@ -335,6 +335,11 @@ BuildingAI.prototype.CheckTargetVisible = function(target)
 
 	var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
 
+	// Entities that are hidden and miraged are considered visible
+	var cmpFogging = Engine.QueryInterface(target, IID_Fogging);
+	if (cmpFogging && cmpFogging.IsMiraged(cmpOwnership.GetOwner()))
+		return true;
+
 	if (cmpRangeManager.GetLosVisibility(target, cmpOwnership.GetOwner(), false) == "hidden")
 		return false;
 
diff --git binaries/data/mods/public/simulation/components/Fogging.js binaries/data/mods/public/simulation/components/Fogging.js
new file mode 100644
index 0000000..87e6907
--- /dev/null
+++ binaries/data/mods/public/simulation/components/Fogging.js
@@ -0,0 +1,165 @@
+const VIS_HIDDEN = 0;
+const VIS_FOGGED = 1;
+const VIS_VISIBLE = 2;
+
+function Fogging() {}
+
+Fogging.prototype.Schema =
+	"<a:help>Allows this entity to be replaced by mirages entities in the fog-of-war.</a:help>" +
+	"<empty/>";
+
+Fogging.prototype.Init = function()
+{
+	this.mirages = [];
+	this.seen = [];
+
+	var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
+	for (var player = 0; player < cmpPlayerManager.GetNumPlayers(); ++player)
+	{
+		this.mirages.push(INVALID_ENTITY);
+		this.seen.push(false);
+	}
+};
+
+Fogging.prototype.LoadMirage = function(player)
+{
+	var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
+	var templateName = "mirage|" + cmpTemplateManager.GetCurrentTemplateName(this.entity);
+	
+	// If this is an entity without visibility (e.g. a foundation), it should be
+	// marked as seen for its owner	
+	var cmpParentOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+	if (cmpParentOwnership && cmpParentOwnership.GetOwner() == player)
+		this.seen[player] = true;
+
+	if (!this.seen[player] || this.mirages[player] != INVALID_ENTITY)
+		return;
+
+	this.mirages[player] = Engine.AddEntity(templateName);
+	var cmpMirage = Engine.QueryInterface(this.mirages[player], IID_Mirage);
+	if (!cmpMirage)
+	{
+		error("Failed to load mirage entity for template " + templateName);
+		this.mirages[player] = INVALID_ENTITY;
+		return;
+	}
+
+	// Setup basic mirage properties
+	cmpMirage.SetPlayer(player);
+	cmpMirage.SetParent(this.entity);
+
+	// Copy cmpOwnership data
+	var cmpMirageOwnership = Engine.QueryInterface(this.mirages[player], IID_Ownership);
+	if (!cmpParentOwnership || !cmpMirageOwnership)
+	{
+		error("Failed to setup the ownership data of the fogged entity " + templateName);
+		return;
+	}
+	cmpMirageOwnership.SetOwner(cmpParentOwnership.GetOwner());
+
+	// Copy cmpPosition data
+	var cmpParentPosition = Engine.QueryInterface(this.entity, IID_Position);
+	var cmpMiragePosition = Engine.QueryInterface(this.mirages[player], IID_Position);
+	if (!cmpParentPosition || !cmpMiragePosition)
+	{
+		error("Failed to setup the position data of the fogged entity " + templateName);
+		return;
+	}
+	if (!cmpParentPosition.IsInWorld())
+		return;
+	var pos = cmpParentPosition.GetPosition();
+	cmpMiragePosition.JumpTo(pos.x, pos.z);
+	var rot = cmpParentPosition.GetRotation();
+	cmpMiragePosition.SetYRotation(rot.y);
+	cmpMiragePosition.SetXZRotation(rot.x, rot.z);
+
+	// Copy cmpVisualActor data
+	var cmpParentVisualActor = Engine.QueryInterface(this.entity, IID_Visual);
+	var cmpMirageVisualActor = Engine.QueryInterface(this.mirages[player], IID_Visual);
+	if (!cmpParentVisualActor || !cmpMirageVisualActor)
+	{
+		error("Failed to setup the visual data of the fogged entity " + templateName);
+		return;
+	}
+	cmpMirageVisualActor.SetActorSeed(cmpParentVisualActor.GetActorSeed());
+
+	// Store valuable information into the mirage component (especially for the GUI)
+	var cmpFoundation = Engine.QueryInterface(this.entity, IID_Foundation);
+	if (cmpFoundation)
+		cmpMirage.AddFoundation(cmpFoundation.GetBuildPercentage());
+
+	var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
+	if (cmpHealth)
+		cmpMirage.AddHealth(
+			cmpHealth.GetMaxHitpoints(), 
+			cmpHealth.GetHitpoints(), 
+			cmpHealth.IsRepairable() && (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints())
+		);
+
+	var cmpResourceSupply = Engine.QueryInterface(this.entity, IID_ResourceSupply);
+	if (cmpResourceSupply)
+		cmpMirage.AddResourceSupply(
+			cmpResourceSupply.GetMaxAmount(), 
+			cmpResourceSupply.GetCurrentAmount(), 
+			cmpResourceSupply.GetType(), 
+			cmpResourceSupply.IsInfinite()
+		);
+};
+
+Fogging.prototype.IsMiraged = function(player)
+{
+	if (player >= this.mirages.length)
+		return false;
+
+	return this.mirages[player] != INVALID_ENTITY;
+};
+
+Fogging.prototype.GetMirage = function(player)
+{
+	if (player >= this.mirages.length)
+		return INVALID_ENTITY;
+
+	return this.mirages[player];
+};
+
+Fogging.prototype.WasSeen = function(player)
+{
+	if (player >= this.seen.length)
+		return false;
+
+	return this.seen[player];
+};
+
+Fogging.prototype.OnVisibilityChanged = function(msg)
+{
+	if (msg.player >= this.mirages.length)
+		return;
+
+	if (msg.newVisibility == VIS_VISIBLE)
+	{
+		this.seen[msg.player] = true;
+
+		// Destroy mirages when we get back into LoS
+		if (this.mirages[msg.player] != INVALID_ENTITY)
+		{
+			Engine.DestroyEntity(this.mirages[msg.player]);
+			this.mirages[msg.player] = INVALID_ENTITY;
+		}
+	}
+
+	// Intermediate LoS state, meaning we must create a mirage
+	if (msg.newVisibility == VIS_FOGGED)
+		this.LoadMirage(msg.player);
+};
+
+Fogging.prototype.OnDestroy = function(msg)
+{
+	for (var player = 0; player < this.mirages.length; ++player)
+	{
+		var cmpMirage = Engine.QueryInterface(this.mirages[player], IID_Mirage);
+		if (cmpMirage)
+			cmpMirage.SetParent(INVALID_ENTITY);
+	}
+};
+
+Engine.RegisterComponentType(IID_Fogging, "Fogging", Fogging);
diff --git binaries/data/mods/public/simulation/components/GuiInterface.js binaries/data/mods/public/simulation/components/GuiInterface.js
index cfca3af..06196ab 100644
--- binaries/data/mods/public/simulation/components/GuiInterface.js
+++ binaries/data/mods/public/simulation/components/GuiInterface.js
@@ -175,6 +175,7 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
 		"alertRaiser": null,
 		"buildEntities": null,
 		"identity": null,
+		"fogging": null,
 		"foundation": null,
 		"garrisonHolder": null,
 		"gate": null,
@@ -190,6 +191,9 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
 		"visibility": null,
 	};
 
+	// Used for several components
+	var cmpMirage = Engine.QueryInterface(ent, IID_Mirage);
+
 	var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
 	if (cmpIdentity)
 	{
@@ -216,6 +220,12 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
 		ret.needsRepair = cmpHealth.IsRepairable() && (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints());
 		ret.needsHeal = !cmpHealth.IsUnhealable();
 	}
+	if (cmpMirage && cmpMirage.Health())
+	{
+		ret.hitpoints = cmpMirage.GetHitpoints();
+		ret.maxHitpoints = cmpMirage.GetMaxHitpoints();
+		ret.needsRepair = cmpMirage.NeedsRepair();
+	}
 
 	var cmpBuilder = Engine.QueryInterface(ent, IID_Builder);
 	if (cmpBuilder)
@@ -251,6 +261,15 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
 		};
 	}
 
+	var cmpFogging = Engine.QueryInterface(ent, IID_Fogging);
+	if (cmpFogging)
+	{
+		if (cmpFogging.IsMiraged(player))
+			ret.fogging = {"mirage": cmpFogging.GetMirage(player)};
+		else
+			ret.fogging = {"mirage": null};
+	}
+
 	var cmpFoundation = Engine.QueryInterface(ent, IID_Foundation);
 	if (cmpFoundation)
 	{
@@ -259,6 +278,12 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
 			"numBuilders": cmpFoundation.GetNumBuilders()
 		};
 	}
+	if (cmpMirage && cmpMirage.Foundation())
+	{
+		ret.foundation = {
+			"progress": cmpMirage.GetBuildPercentage()
+		};
+	}
 
 	var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
 	if (cmpOwnership)
@@ -342,6 +367,7 @@ GuiInterface.prototype.GetExtendedEntityState = function(player, ent)
 		"barterMarket": null,
 		"buildingAI": null,
 		"healer": null,
+		"mirage": null,
 		"obstruction": null,
 		"turretParent":null,
 		"promotion": null,
@@ -351,6 +377,10 @@ GuiInterface.prototype.GetExtendedEntityState = function(player, ent)
 		"resourceSupply": null,
 	};
 
+	var cmpMirage = Engine.QueryInterface(ent, IID_Mirage);
+	if (cmpMirage)
+		ret.mirage = true;
+
 	var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
 
 	var cmpAttack = Engine.QueryInterface(ent, IID_Attack);
@@ -445,6 +475,15 @@ GuiInterface.prototype.GetExtendedEntityState = function(player, ent)
 			"gatherers": cmpResourceSupply.GetGatherers()
 		};
 	}
+	if (cmpMirage && cmpMirage.ResourceSupply())
+	{
+		ret.resourceSupply = {
+			"max": cmpMirage.GetMaxAmount(),
+			"amount": cmpMirage.GetAmount(),
+			"type": cmpMirage.GetType(),
+			"isInfinite": cmpMirage.IsInfinite()
+		};
+	}
 
 	var cmpResourceGatherer = Engine.QueryInterface(ent, IID_ResourceGatherer);
 	if (cmpResourceGatherer)
diff --git binaries/data/mods/public/simulation/components/Mirage.js binaries/data/mods/public/simulation/components/Mirage.js
new file mode 100644
index 0000000..9306149
--- /dev/null
+++ binaries/data/mods/public/simulation/components/Mirage.js
@@ -0,0 +1,149 @@
+const VIS_HIDDEN = 0;
+const VIS_FOGGED = 1;
+const VIS_VISIBLE = 2;
+
+function Mirage() {}
+
+Mirage.prototype.Schema =
+	"<a:help>Mirage entities replace real entities in the fog-of-war.</a:help>" +
+	"<empty/>";
+
+Mirage.prototype.Init = function()
+{
+	this.player = null;
+	this.parent = INVALID_ENTITY;
+
+	this.foundation = false;
+	this.buildPercentage = null;
+
+	this.health = false;
+	this.maxHitpoints = null;
+	this.hitpoints = null;
+	this.needsRepair = null;
+
+	this.resourceSupply = false;
+	this.maxAmount = null;
+	this.amount = null;
+	this.type = null;
+	this.isInfinite = null;
+};
+
+Mirage.prototype.SetParent = function(ent)
+{
+	this.parent = ent;
+};
+
+Mirage.prototype.GetPlayer = function()
+{
+	return this.player;
+};
+
+Mirage.prototype.SetPlayer = function(player)
+{
+	this.player = player;
+};
+
+// ============================
+// Parent entity data
+
+// Foundation data
+
+Mirage.prototype.AddFoundation = function(buildPercentage)
+{
+	this.foundation = true;
+	this.buildPercentage = buildPercentage;
+};
+
+Mirage.prototype.Foundation = function()
+{
+	return this.foundation;
+};
+
+Mirage.prototype.GetBuildPercentage = function()
+{
+	return this.buildPercentage;
+};
+
+// Health data
+
+Mirage.prototype.AddHealth = function(maxHitpoints, hitpoints, needsRepair)
+{
+	this.health = true;
+	this.maxHitpoints = maxHitpoints;
+	this.hitpoints = Math.ceil(hitpoints);
+	this.needsRepair = needsRepair;
+};
+
+Mirage.prototype.Health = function()
+{
+	return this.health;
+};
+
+Mirage.prototype.GetMaxHitpoints = function()
+{
+	return this.maxHitpoints;
+};
+
+Mirage.prototype.GetHitpoints = function()
+{
+	return this.hitpoints;
+};
+
+Mirage.prototype.NeedsRepair = function()
+{
+	return this.needsRepair;
+};
+
+// ResourceSupply data
+
+Mirage.prototype.AddResourceSupply = function(maxAmount, amount, type, isInfinite)
+{
+	this.resourceSupply = true;
+	this.maxAmount = maxAmount;
+	this.amount = amount;
+	this.type = type;
+	this.isInfinite = isInfinite;
+};
+
+Mirage.prototype.ResourceSupply = function()
+{
+	return this.resourceSupply;
+};
+
+Mirage.prototype.GetMaxAmount = function()
+{
+	return this.maxAmount;
+};
+
+Mirage.prototype.GetAmount = function()
+{
+	return this.amount;
+};
+
+Mirage.prototype.GetType = function()
+{
+	return this.type;
+};
+
+Mirage.prototype.IsInfinite = function()
+{
+	return this.isInfinite;
+};
+
+// ============================
+
+Mirage.prototype.OnVisibilityChanged = function(msg)
+{
+	if (msg.player == this.player && msg.newVisibility == VIS_VISIBLE && this.parent == INVALID_ENTITY)
+		Engine.DestroyEntity(this.entity);
+};
+
+Mirage.prototype.OnDestroy = function(msg)
+{
+	if (this.parent == INVALID_ENTITY)
+		return;
+
+	Engine.BroadcastMessage(MT_EntityRenamed, { entity: this.entity, newentity: this.parent });
+};
+
+Engine.RegisterComponentType(IID_Mirage, "Mirage", Mirage);
diff --git binaries/data/mods/public/simulation/components/ResourceGatherer.js binaries/data/mods/public/simulation/components/ResourceGatherer.js
index 201eb67..2d5fc4c 100644
--- binaries/data/mods/public/simulation/components/ResourceGatherer.js
+++ binaries/data/mods/public/simulation/components/ResourceGatherer.js
@@ -243,12 +243,16 @@ ResourceGatherer.prototype.GetTargetGatherRate = function(target)
 {
 	var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
 
+	var type;
 	var cmpResourceSupply = Engine.QueryInterface(target, IID_ResourceSupply);
-	if (!cmpResourceSupply)
+	var cmpMirage = Engine.QueryInterface(target, IID_Mirage);
+	if (cmpResourceSupply)
+		type = cmpResourceSupply.GetType();
+	else if (cmpMirage && cmpMirage.ResourceSupply())
+		type = cmpMirage.GetType();
+	else
 		return 0;
 
-	var type = cmpResourceSupply.GetType();
-
 	var rates = this.GetGatherRates();
 
 	var rate;
@@ -261,7 +265,12 @@ ResourceGatherer.prototype.GetTargetGatherRate = function(target)
 		rate = rates[type.generic] / cmpPlayer.GetCheatTimeMultiplier();
 	}
 
+	if (cmpMirage)
+		return rate || 0;
+
 	// Apply diminishing returns with more gatherers, for e.g. infinite farms. For most resources this has no effect. (GetDiminishingReturns will return null.)
+	// We can assume that for resources that are miraged this is the case. (else just add the diminishing returns data to the mirage data and remove the
+	// early return above)
 	// Note to people looking to change <DiminishingReturns> in a template: This is a bit complicated. Basically, the lower that number is
 	// the steeper diminishing returns will be. I suggest playing around with Wolfram Alpha or a graphing calculator a bit.
 	// In each of the following links, replace 0.65 with the gather rate of your worker for the resource with diminishing returns and
diff --git binaries/data/mods/public/simulation/components/UnitAI.js binaries/data/mods/public/simulation/components/UnitAI.js
index 8aa0c0a..22e1190 100644
--- binaries/data/mods/public/simulation/components/UnitAI.js
+++ binaries/data/mods/public/simulation/components/UnitAI.js
@@ -1987,7 +1987,9 @@ UnitAI.prototype.UnitFsmSpec = {
 					// check that we can gather from the resource we're supposed to gather from.
 					var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
 					var cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
-					if (!cmpSupply || !cmpSupply.AddGatherer(cmpOwnership.GetOwner(), this.entity))
+					var cmpMirage = Engine.QueryInterface(this.gatheringTarget, IID_Mirage);
+					if ((!cmpMirage || !cmpMirage.ResourceSupply()) &&
+					    (!cmpSupply || !cmpSupply.AddGatherer(cmpOwnership.GetOwner(), this.entity)))
 					{
 						// Save the current order's data in case we need it later
 						var oldType = this.order.data.type;
@@ -3896,6 +3898,10 @@ UnitAI.prototype.TargetIsAlive = function(ent)
 	if (cmpFormation)
 		return true;
 
+	var cmpMirage = Engine.QueryInterface(ent, IID_Mirage);
+	if (cmpMirage)
+		return true;
+
 	var cmpHealth = Engine.QueryInterface(ent, IID_Health);
 	if (!cmpHealth)
 		return false;
@@ -4386,6 +4392,11 @@ UnitAI.prototype.CheckTargetVisible = function(target)
 	if (!cmpRangeManager)
 		return false;
 
+	// Entities that are hidden and miraged are considered visible
+	var cmpFogging = Engine.QueryInterface(target, IID_Fogging);
+	if (cmpFogging && cmpFogging.IsMiraged(cmpOwnership.GetOwner()))
+		return true;
+
 	if (cmpRangeManager.GetLosVisibility(target, cmpOwnership.GetOwner(), false) == "hidden")
 		return false;
 
@@ -5007,8 +5018,15 @@ UnitAI.prototype.PerformGather = function(target, queued, force)
 	// Save the resource type now, so if the resource gets destroyed
 	// before we process the order then we still know what resource
 	// type to look for more of
+	var type;
 	var cmpResourceSupply = Engine.QueryInterface(target, IID_ResourceSupply);
-	var type = cmpResourceSupply.GetType();
+	var cmpMirage = Engine.QueryInterface(target, IID_Mirage);
+	if (cmpResourceSupply)
+		type = cmpResourceSupply.GetType();
+	else if (cmpMirage && cmpMirage.ResourceSupply())
+		type = cmpMirage.GetType();
+	else
+		error("CanGather allowed gathering from invalid entity");
 
 	// Also save the target entity's template, so that if it's an animal,
 	// we won't go from hunting slow safe animals to dangerous fast ones
@@ -5552,9 +5570,10 @@ UnitAI.prototype.CanGather = function(target)
 {
 	if (this.IsTurret())
 		return false;
-	// The target must be a valid resource supply.
+	// The target must be a valid resource supply, or the mirage of one.
 	var cmpResourceSupply = Engine.QueryInterface(target, IID_ResourceSupply);
-	if (!cmpResourceSupply)
+	var cmpMirage = Engine.QueryInterface(target, IID_Mirage);
+	if (!cmpResourceSupply && !(cmpMirage && cmpMirage.ResourceSupply()))
 		return false;
 
 	// Formation controllers should always respond to commands
diff --git binaries/data/mods/public/simulation/components/interfaces/Messages.js binaries/data/mods/public/simulation/components/interfaces/Messages.js
index c4f6c6f..ae3d8fc 100644
--- binaries/data/mods/public/simulation/components/interfaces/Messages.js
+++ binaries/data/mods/public/simulation/components/interfaces/Messages.js
@@ -1,8 +1,9 @@
 /**
  * Broadcast message 
  * sent when one entity is changed to other:
- * from Foundation component when building constuction is done
- * and from Promotion component when unit is promoted
+ * - from Foundation component when building construction is done
+ * - from Promotion component when unit is promoted
+ * - from Mirage component when a fogged entity is re-discovered
  * Data: { entity: <integer>, newentity: <integer> }
  */
 Engine.RegisterMessageType("EntityRenamed");
diff --git binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
index 0ae49bc..197785d 100644
--- binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
+++ binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
@@ -398,6 +398,7 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetEntityState(-1, 10), {
 		visibleClasses: ["class3", "class4"],
 		selectionGroupName: "Selection Group Name",
 	},
+	fogging: null,
 	foundation: null,
 	garrisonHolder: null,
 	gate: null,
@@ -425,6 +426,7 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetExtendedEntityState(-1, 10), {
 	},
 	buildingAI: null,
 	healer: null,
+	mirage: null,
 	obstruction: null,
 	turretParent: null,
 	promotion: null,
diff --git binaries/data/mods/public/simulation/helpers/Commands.js binaries/data/mods/public/simulation/helpers/Commands.js
index 6cc67a3..90e28c5 100644
--- binaries/data/mods/public/simulation/helpers/Commands.js
+++ binaries/data/mods/public/simulation/helpers/Commands.js
@@ -974,6 +974,11 @@ function TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd)
 			"queued": cmd.queued
 		});
 	}
+	
+	// Load a mirage for the owner of this new entity
+	var cmpFogging = Engine.QueryInterface(ent, IID_Fogging)
+	if (cmpFogging)
+		cmpFogging.LoadMirage(player);
 
 	return ent;
 }
diff --git binaries/data/mods/public/simulation/templates/template_gaia.xml binaries/data/mods/public/simulation/templates/template_gaia.xml
index 27744da..7a5f54b 100644
--- binaries/data/mods/public/simulation/templates/template_gaia.xml
+++ binaries/data/mods/public/simulation/templates/template_gaia.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Entity parent="template_entity_quasi">
+  <Fogging/>
   <Identity>
     <Civ>gaia</Civ>
     <GenericName>Gaia</GenericName>
diff --git binaries/data/mods/public/simulation/templates/template_structure.xml binaries/data/mods/public/simulation/templates/template_structure.xml
index 6272e33..47cc0ef 100644
--- binaries/data/mods/public/simulation/templates/template_structure.xml
+++ binaries/data/mods/public/simulation/templates/template_structure.xml
@@ -37,6 +37,7 @@
     <SinkRate>3.0</SinkRate>
     <SinkAccel>9.8</SinkAccel>
   </Decay>
+  <Fogging/>
   <Health>
     <DeathType>corpse</DeathType>
     <RegenRate>0</RegenRate>
diff --git source/ps/TemplateLoader.cpp source/ps/TemplateLoader.cpp
index 4ef7374..3c985fc 100644
--- source/ps/TemplateLoader.cpp
+++ source/ps/TemplateLoader.cpp
@@ -80,6 +80,21 @@ bool CTemplateLoader::LoadTemplateFile(const std::string& templateName, int dept
 		return true;
 	}
 
+	// Handle special case "mirage|foo"
+	if (templateName.find("mirage|") == 0)
+	{
+		// Load the base entity template, if it wasn't already loaded
+		std::string baseName = templateName.substr(7);
+		if (!LoadTemplateFile(baseName, depth+1))
+		{
+			LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str());
+			return false;
+		}
+		// Copy a subset to the requested template
+		CopyMirageSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]);
+		return true;
+	}
+
 	// Handle special case "foundation|foo"
 	if (templateName.find("foundation|") == 0)
 	{
@@ -383,6 +398,40 @@ void CTemplateLoader::CopyPreviewSubset(CParamNode& out, const CParamNode& in, b
 	}
 }
 
+void CTemplateLoader::CopyMirageSubset(CParamNode& out, const CParamNode& in)
+{
+	// Currently used for mirage entities replacing real ones in fog-of-war
+
+	std::set<std::string> permittedComponentTypes;
+	permittedComponentTypes.insert("Footprint");
+	permittedComponentTypes.insert("Minimap");
+	permittedComponentTypes.insert("Ownership");
+	permittedComponentTypes.insert("Position");
+	permittedComponentTypes.insert("Selectable");
+	permittedComponentTypes.insert("VisualActor");
+
+	CParamNode::LoadXMLString(out, "<Entity/>");
+	out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes);
+
+	// Select a subset of identity data. We don't want to have, for example, a CC mirage
+	// that has also the CC class and then prevents construction of other CCs
+	std::set<std::string> identitySubset;
+	identitySubset.insert("Civ");
+	identitySubset.insert("GenericName");
+	identitySubset.insert("SpecificName");
+	identitySubset.insert("Tooltip");
+	identitySubset.insert("History");
+	identitySubset.insert("Icon");
+	CParamNode identity;
+	CParamNode::LoadXMLString(identity, "<Identity/>");
+	identity.CopyFilteredChildrenOfChild(in.GetChild("Entity"), "Identity", identitySubset);
+	CParamNode::LoadXMLString(out, ("<Entity>"+utf8_from_wstring(identity.ToXML())+"</Entity>").c_str());
+
+	// Set the entity as mirage entity
+	CParamNode::LoadXMLString(out, "<Entity><Mirage/></Entity>");
+	CParamNode::LoadXMLString(out, "<Entity><Vision><Range>0</Range><RetainInFog>true</RetainInFog><AlwaysVisible>false</AlwaysVisible></Vision></Entity>");
+}
+
 void CTemplateLoader::CopyFoundationSubset(CParamNode& out, const CParamNode& in)
 {
 	// TODO: this is all kind of yucky and hard-coded; it'd be nice to have a more generic
@@ -397,6 +446,7 @@ void CTemplateLoader::CopyFoundationSubset(CParamNode& out, const CParamNode& in
 	permittedComponentTypes.insert("Obstruction");
 	permittedComponentTypes.insert("Selectable");
 	permittedComponentTypes.insert("Footprint");
+	permittedComponentTypes.insert("Fogging");
 	permittedComponentTypes.insert("Armour");
 	permittedComponentTypes.insert("Health");
 	permittedComponentTypes.insert("StatusBars");
diff --git source/ps/TemplateLoader.h source/ps/TemplateLoader.h
index 7217b69..30888b6 100644
--- source/ps/TemplateLoader.h
+++ source/ps/TemplateLoader.h
@@ -79,6 +79,12 @@ private:
 	void CopyPreviewSubset(CParamNode& out, const CParamNode& in, bool corpse);
 
 	/** 
+	 * Copy the components of an entity template necessary for a fogged "mirage"
+	 * entity (position, actor) into a new entity template
+	 */
+	void CopyMirageSubset(CParamNode& out, const CParamNode& in);
+
+	/** 
 	 * Copy the components of an entity template necessary for a construction foundation
 	 * (position, actor, armour, health, etc) into a new entity template
 	 */
diff --git source/simulation2/TypeList.h source/simulation2/TypeList.h
index 8525a1c..eb5ac6a 100644
--- source/simulation2/TypeList.h
+++ source/simulation2/TypeList.h
@@ -81,6 +81,9 @@ COMPONENT(CommandQueue)
 INTERFACE(Decay)
 COMPONENT(Decay)
 
+INTERFACE(Fogging)
+COMPONENT(FoggingScripted)
+
 // Note: The VisualActor component relies on this component being initialized before itself, in order to support using
 // an entity's footprint shape for the selection boxes. This dependency is not strictly necessary, but it does avoid
 // some extra plumbing code to set up on-demand initialization. If you find yourself forced to break this dependency, 
@@ -97,6 +100,9 @@ COMPONENT(IdentityScripted)
 INTERFACE(Minimap)
 COMPONENT(Minimap)
 
+INTERFACE(Mirage)
+COMPONENT(MirageScripted)
+
 INTERFACE(Motion)
 COMPONENT(MotionBall)
 COMPONENT(MotionScripted)
diff --git source/simulation2/components/CCmpRangeManager.cpp source/simulation2/components/CCmpRangeManager.cpp
index 4fca628..f61deab 100644
--- source/simulation2/components/CCmpRangeManager.cpp
+++ source/simulation2/components/CCmpRangeManager.cpp
@@ -23,6 +23,9 @@
 #include "ICmpTerrain.h"
 #include "simulation2/system/EntityMap.h"
 #include "simulation2/MessageTypes.h"
+#include "simulation2/components/ICmpFogging.h"
+#include "simulation2/components/ICmpMirage.h"
+#include "simulation2/components/ICmpOwnership.h"
 #include "simulation2/components/ICmpPosition.h"
 #include "simulation2/components/ICmpTerritoryManager.h"
 #include "simulation2/components/ICmpVision.h"
@@ -564,7 +567,6 @@ public:
 		case MT_Update:
 		{
 			m_DebugOverlayDirty = true;
-			UpdateTerritoriesLos();
 			ExecuteActiveQueries();
 			UpdateVisibilityData();
 			break;
@@ -1401,21 +1403,50 @@ public:
 				return VIS_VISIBLE;
 		}
 
+		// Mirage entities, whatever their position, are visible for one specific player
+		CmpPtr<ICmpMirage> cmpMirage(ent);
+		if (cmpMirage && cmpMirage->GetPlayer() != player)
+			return VIS_HIDDEN;
+
 		// Visible if within a visible region
 		CLosQuerier los(GetSharedLosMask(player), m_LosState, m_TerrainVerticesPerSide);
 
 		if (los.IsVisible(i, j))
 			return VIS_VISIBLE;
 
+		if (!los.IsExplored(i, j))			
+			return VIS_HIDDEN;
+			
 		// Fogged if the 'retain in fog' flag is set, and in a non-visible explored region
-		if (los.IsExplored(i, j))
+		CmpPtr<ICmpVision> cmpVision(ent);
+		if (!forceRetainInFog && !(cmpVision && cmpVision->GetRetainInFog()))
+			return VIS_HIDDEN;
+
+		if (cmpMirage && cmpMirage->GetPlayer() == player)
+			return VIS_FOGGED;
+
+		CmpPtr<ICmpOwnership> cmpOwnership(ent);
+		if (!cmpOwnership)
+			return VIS_VISIBLE;
+		
+		if (cmpOwnership->GetOwner() == player)
 		{
-			CmpPtr<ICmpVision> cmpVision(ent);
-			if (forceRetainInFog || (cmpVision && cmpVision->GetRetainInFog()))
+			CmpPtr<ICmpFogging> cmpFogging(ent);
+			if (!cmpFogging)
+				return VIS_VISIBLE;
+			
+			// Fogged entities must not disappear while the mirage is not ready
+			if (!cmpFogging->IsMiraged(player))
 				return VIS_FOGGED;
-		}
 
-		// Otherwise not visible
+			return VIS_HIDDEN;
+		}
+	
+		// Fogged entities must not disappear while the mirage is not ready
+		CmpPtr<ICmpFogging> cmpFogging(ent);
+		if (cmpFogging && cmpFogging->WasSeen(player) && !cmpFogging->IsMiraged(player))
+			return VIS_FOGGED;
+				
 		return VIS_HIDDEN;
 	}
 
@@ -1487,18 +1518,29 @@ public:
 		EntityMap<EntityData>::iterator itEnts = m_EntityData.find(ent);
 		if (itEnts == m_EntityData.end())
 			return;
+
+		std::vector<u8> oldVisibilities;
+		std::vector<u8> newVisibilities;
 		
-		for (player_id_t player = 1; player <= MAX_LOS_PLAYER_ID; ++player)
+		for (player_id_t player = 1; player < MAX_LOS_PLAYER_ID+1; ++player)
 		{
 			u8 oldVis = (itEnts->second.visibilities >> (2*(player-1))) & 0x3;
 			u8 newVis = GetLosVisibility(itEnts->first, player, false);
+			
+			oldVisibilities.push_back(oldVis);
+			newVisibilities.push_back(newVis);
 
 			if (oldVis != newVis)
-			{
-				CMessageVisibilityChanged msg(player, ent, oldVis, newVis);
-				GetSimContext().GetComponentManager().PostMessage(ent, msg);
 				itEnts->second.visibilities = (itEnts->second.visibilities & ~(0x3 << 2*(player-1))) | (newVis << 2*(player-1));
-			}
+		}
+
+		for (player_id_t player = 1; player < MAX_LOS_PLAYER_ID+1; ++player)
+		{
+			if (oldVisibilities[player-1] == newVisibilities[player-1])
+				continue;
+			
+			CMessageVisibilityChanged msg(player, ent, oldVisibilities[player-1], newVisibilities[player-1]);
+			GetSimContext().GetComponentManager().PostMessage(ent, msg);
 		}
 	}
 
diff --git source/simulation2/components/ICmpFogging.cpp source/simulation2/components/ICmpFogging.cpp
new file mode 100644
index 0000000..a981adb
--- /dev/null
+++ source/simulation2/components/ICmpFogging.cpp
@@ -0,0 +1,44 @@
+/* Copyright (C) 2014 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 "precompiled.h"
+
+#include "ICmpFogging.h"
+
+#include "simulation2/scripting/ScriptComponent.h"
+#include "simulation2/system/InterfaceScripted.h"
+
+BEGIN_INTERFACE_WRAPPER(Fogging)
+END_INTERFACE_WRAPPER(Fogging)
+
+class CCmpFoggingScripted : public ICmpFogging
+{
+public:
+	DEFAULT_SCRIPT_WRAPPER(FoggingScripted)
+
+	virtual bool WasSeen(player_id_t player)
+	{
+		return m_Script.Call<bool>("WasSeen", player);
+	}
+
+	virtual bool IsMiraged(player_id_t player)
+	{
+		return m_Script.Call<bool>("IsMiraged", player);
+	}
+};
+
+REGISTER_COMPONENT_SCRIPT_WRAPPER(FoggingScripted)
diff --git source/simulation2/components/ICmpFogging.h source/simulation2/components/ICmpFogging.h
new file mode 100644
index 0000000..cd759f6
--- /dev/null
+++ source/simulation2/components/ICmpFogging.h
@@ -0,0 +1,39 @@
+/* Copyright (C) 2014 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/>.
+ */
+
+#ifndef INCLUDED_ICMPFOGGING
+#define INCLUDED_ICMPFOGGING
+
+#include "simulation2/system/Interface.h"
+
+#include "simulation2/helpers/Player.h"
+
+/**
+ * Handles the fogging of out-of-sight enemy entities, by creating mirage
+ * entities.
+ * This allows hiding changes, especially destruction status or health.
+ */
+class ICmpFogging : public IComponent
+{
+public:
+	virtual bool WasSeen(player_id_t player) = 0;
+	virtual bool IsMiraged(player_id_t player) = 0;
+
+	DECLARE_INTERFACE_TYPE(Fogging)
+};
+
+#endif // INCLUDED_ICMPFOGGING
diff --git source/simulation2/components/ICmpMirage.cpp source/simulation2/components/ICmpMirage.cpp
new file mode 100644
index 0000000..80ceaca
--- /dev/null
+++ source/simulation2/components/ICmpMirage.cpp
@@ -0,0 +1,39 @@
+/* Copyright (C) 2014 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 "precompiled.h"
+
+#include "ICmpMirage.h"
+
+#include "simulation2/scripting/ScriptComponent.h"
+#include "simulation2/system/InterfaceScripted.h"
+
+BEGIN_INTERFACE_WRAPPER(Mirage)
+END_INTERFACE_WRAPPER(Mirage)
+
+class CCmpMirageScripted : public ICmpMirage
+{
+public:
+	DEFAULT_SCRIPT_WRAPPER(MirageScripted)
+
+	virtual player_id_t GetPlayer()
+	{
+		return m_Script.Call<player_id_t>("GetPlayer");
+	}
+};
+
+REGISTER_COMPONENT_SCRIPT_WRAPPER(MirageScripted)
diff --git source/simulation2/components/ICmpMirage.h source/simulation2/components/ICmpMirage.h
new file mode 100644
index 0000000..5edb227
--- /dev/null
+++ source/simulation2/components/ICmpMirage.h
@@ -0,0 +1,37 @@
+/* Copyright (C) 2014 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/>.
+ */
+
+#ifndef INCLUDED_ICMPMIRAGE
+#define INCLUDED_ICMPMIRAGE
+
+#include "simulation2/system/Interface.h"
+
+#include "simulation2/helpers/Player.h"
+
+/**
+ * Component allowing mirage entities to communicate with their parent entity.
+ * See ICmpFogging.
+ */
+class ICmpMirage : public IComponent
+{
+public:
+	virtual player_id_t GetPlayer() = 0;
+
+	DECLARE_INTERFACE_TYPE(Mirage)
+};
+
+#endif // INCLUDED_ICMPMIRAGE
