diff --git a/build/config_editor.cfg b/build/config_editor.cfg
index 8a02e596e8f10929760a7b9cef98d2a6a78508ea..50e8daa52e110c27c8ded7a918282ad709103d2b 100644
--- a/build/config_editor.cfg
+++ b/build/config_editor.cfg
@@ -15,3 +15,31 @@ hud_rangefinder 0
 cl_mousesense 4.5
 
 dev_show_triggers 1
+
+dev_reset_world_on_run 1
+
+alias run_level "Start level from editor"
+	echo "Switching to game"
+	terrax_detach_3d 1
+	gmode ingame
+	dev_show_triggers 0
+	hud_draw 1
+//	hud_crosshair 1
+//	hud_rangefinder 1
+	exec ../config_game.cfg
+	exec ../config_game_user.cfg
+	bind escape end_level
+	spawn
+endalias
+
+alias end_level
+	echo "Switching to editor"
+	cl_grab_cursor 0
+	terrax_detach_3d 0
+//	hud_draw 0
+//	hud_crosshair 0
+//	hud_rangefinder 0
+	gmode editor
+//	dev_show_triggers 1
+	exec ../config_editor.cfg
+endalias
diff --git a/proj/sxgame/vs2013/sxgame.vcxproj b/proj/sxgame/vs2013/sxgame.vcxproj
index a867ed7d5d6d21a2bac09eec943028b698ef42e8..edc4d885a4c8ac5ab95a75035f4edd73fc81f15f 100644
--- a/proj/sxgame/vs2013/sxgame.vcxproj
+++ b/proj/sxgame/vs2013/sxgame.vcxproj
@@ -248,6 +248,7 @@
     <ClInclude Include="..\..\..\source\common\string_utils.h" />
     <ClInclude Include="..\..\..\source\game\BaseHandle.h" />
     <ClInclude Include="..\..\..\source\game\BaseLight.h" />
+    <ClInclude Include="..\..\..\source\game\Baseline.h" />
     <ClInclude Include="..\..\..\source\game\BaseMag.h" />
     <ClInclude Include="..\..\..\source\game\BaseScope.h" />
     <ClInclude Include="..\..\..\source\game\BaseSilencer.h" />
diff --git a/proj/sxgame/vs2013/sxgame.vcxproj.filters b/proj/sxgame/vs2013/sxgame.vcxproj.filters
index 71f76362f986dc8aadad08bdd70e8444ce6fbbf2..605c80cc668bde7b56b89a5248edb8271ef63d9e 100644
--- a/proj/sxgame/vs2013/sxgame.vcxproj.filters
+++ b/proj/sxgame/vs2013/sxgame.vcxproj.filters
@@ -470,5 +470,8 @@
     <ClInclude Include="..\..\..\source\game\SoundPlayer.h">
       <Filter>Header Files\ents</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\..\source\game\Baseline.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
 </Project>
\ No newline at end of file
diff --git a/source/game/Baseline.h b/source/game/Baseline.h
new file mode 100644
index 0000000000000000000000000000000000000000..103516a477f98ce5778bff7d5231ee92bf5ed4df
--- /dev/null
+++ b/source/game/Baseline.h
@@ -0,0 +1,43 @@
+
+/***********************************************************
+Copyright © Vitaliy Buturlin, Evgeny Danilovich, 2017, 2018
+See the license in LICENSE
+***********************************************************/
+
+#include "EntityManager.h"
+#include <common/assotiativearray.h>
+
+#ifndef __BASELINE_H
+#define __BASELINE_H
+
+class CBaseline
+{
+	friend class CEntityManager;
+
+	struct ent_record_s
+	{
+		UINT m_id;
+		String m_sClassname;
+		AssotiativeArray<String, String> m_mProps;
+	};
+
+public:
+
+	ID getId()
+	{
+		return(m_id);
+	}
+
+protected:
+	ID m_id;
+	Array<ent_record_s> m_aRecords;
+
+	CBaseline(ID id):m_id(id)
+	{
+	}
+	~CBaseline()
+	{
+	}
+};
+
+#endif
diff --git a/source/game/EntityManager.cpp b/source/game/EntityManager.cpp
index 656172eead6c27bb4b2951b73aac2bef40057747..f2d82f7da09e995798bf2211b3fad82719c3d5b8 100644
--- a/source/game/EntityManager.cpp
+++ b/source/game/EntityManager.cpp
@@ -8,28 +8,52 @@ See the license in LICENSE
 
 #include "BaseEntity.h"
 
+#include "Baseline.h"
+
 #include <core/sxcore.h>
 
+void CEntityManager::CCVarEventListener::onEvent(const XEventCvarChanged *pEvent)
+{
+	static const bool *game_time_running = m_pCore->getConsole()->getPCVarBool("game_time_running");
+
+	if(game_time_running == pEvent->pCvar)
+	{
+		m_pMgr->setTimeRunning(*game_time_running);
+	}
+}
+
+//##########################################################################
+
 CEntityManager::CEntityManager():
-	m_iThreadNum(1),
-	m_pDefaultsConf(NULL),
-	m_pDynClassConf(NULL)
+	m_cvarListener(this, Core_GetIXCore())
 {
 	loadDefaults();
 	loadDynClasses();
 
 	Core_0RegisterConcmdClsArg("ent_dump", this, (SXCONCMDCLSARG)&CEntityManager::dumpList);
 	Core_0RegisterConcmdClsArg("ent_kv", this, (SXCONCMDCLSARG)&CEntityManager::entKV);
+
+	Core_GetIXCore()->getConsole()->registerCVar("game_time_running", false, "Is game time running", FCVAR_NOTIFY);
+
+	Core_GetIXCore()->getEventChannel<XEventCvarChanged>(EVENT_CVAR_CHANGED_GUID)->addListener(&m_cvarListener);
 }
 
 CEntityManager::~CEntityManager()
 {
+	Core_GetIXCore()->getEventChannel<XEventCvarChanged>(EVENT_CVAR_CHANGED_GUID)->removeListener(&m_cvarListener);
+
 	mem_release(m_pDynClassConf);
 	mem_release(m_pDefaultsConf);
 }
 
 void CEntityManager::update(int thread)
 {
+	static const bool *game_time_running = Core_GetIXCore()->getConsole()->getPCVarBool("game_time_running");
+	if(!*game_time_running)
+	{
+		return;
+	}
+
 	time_point tNow = std::chrono::high_resolution_clock::now();
 	timeout_t * t;
 	timeout_output_t * to;
@@ -97,10 +121,9 @@ void CEntityManager::setThreadNum(int num)
 	}
 }
 
-void CEntityManager::sync()
+void CEntityManager::finalRemove()
 {
 	CBaseEntity * pEnt;
-
 	// do not store m_vEntRemoveList.size()! It can change during iteration
 	for(int i = 0; i < (int)m_vEntRemoveList.size(); ++i)
 	{
@@ -112,6 +135,13 @@ void CEntityManager::sync()
 		}
 	}
 	m_vEntRemoveList.clearFast();
+}
+
+void CEntityManager::sync()
+{
+	CBaseEntity * pEnt;
+
+	finalRemove();
 
 	for(int i = 0, l = m_vTimeout.size(); i < l; ++i)
 	{
@@ -272,12 +302,12 @@ ID CEntityManager::setTimeout(void(CBaseEntity::*func)(float dt), CBaseEntity *p
 	t.func = func;
 	t.pEnt = pEnt;
 
-	t.fStartTime = t.fNextTime = std::chrono::high_resolution_clock::now();
+	t.fStartTime = t.fNextTime = m_isTimeRunning ? std::chrono::high_resolution_clock::now() : m_tStopPoint;
 	t.fNextTime += std::chrono::microseconds((long long)(delay * 1000000.0f));
 	
 	ID id;
 	{
-		ScopedLock lock(m_mxTimeout);
+		ScopedSpinLock lock(m_mxTimeout);
 
 		if(m_vFreeTimeout.size())
 		{
@@ -301,12 +331,12 @@ ID CEntityManager::setInterval(void(CBaseEntity::*func)(float dt), CBaseEntity *
 	t.func = func;
 	t.pEnt = pEnt;
 
-	t.fStartTime = t.fNextTime = std::chrono::high_resolution_clock::now();
+	t.fStartTime = t.fNextTime = m_isTimeRunning ? std::chrono::high_resolution_clock::now() : m_tStopPoint;
 	t.fNextTime += std::chrono::microseconds((long long)(delay * 1000000.0f));
 
 	ID id;
 	{
-		ScopedLock lock(m_mxInterval);
+		ScopedSpinLock lock(m_mxInterval);
 		if(m_vFreeInterval.size())
 		{
 			id = m_vFreeInterval[0];
@@ -724,7 +754,7 @@ void CEntityManager::dumpList(int argc, const char **argv)
 		}
 		if(!filter[0] || strstr(pEnt->getClassName(), filter))
 		{
-			printf(" " COLOR_LGREEN "%4d" COLOR_GREEN " | " COLOR_LGREEN "%24s" COLOR_GREEN " | " COLOR_LGREEN "%16s" COLOR_GREEN " |\n", i, pEnt->getClassName(), pEnt->getName());
+			printf(" " COLOR_LGREEN "%-4d" COLOR_GREEN " | " COLOR_LGREEN "%-24s" COLOR_GREEN " | " COLOR_LGREEN "%-16s" COLOR_GREEN " |\n", i, pEnt->getClassName(), pEnt->getName());
 		}
 	}
 
@@ -846,7 +876,7 @@ void CEntityManager::setOutputTimeout(named_output_t *pOutput, inputdata_t *pDat
 	t.pOutput = pOutput;
 	t.data = *pData;
 
-	t.fStartTime = t.fNextTime = std::chrono::high_resolution_clock::now();
+	t.fStartTime = t.fNextTime = m_isTimeRunning ? std::chrono::high_resolution_clock::now() : m_tStopPoint;
 	t.fNextTime += std::chrono::microseconds((long long)((pOutput->useRandomDelay ? randf(pOutput->fDelay, pOutput->fDelayTo) : pOutput->fDelay) * 1000000.0f));
 
 	m_vOutputTimeout.push_back(t);
@@ -897,3 +927,299 @@ bool CEntityManager::isEditorMode()
 {
 	return(m_isEditorMode);
 }
+
+void CEntityManager::setTimeRunning(bool set)
+{
+	time_point tNow = std::chrono::high_resolution_clock::now();
+	m_isTimeRunning = set;
+	if(set)
+	{
+		auto tDelta = tNow - m_tStopPoint;
+
+		timeout_t *t;
+		timeout_output_t *to;
+
+		for(UINT i = 0, l = m_vOutputTimeout.size(); i < l; ++i)
+		{
+			to = &m_vOutputTimeout[i];
+			if(to->status == TS_WAIT)
+			{
+				to->fNextTime += tDelta;
+				to->fStartTime += tDelta;
+			}
+		}
+
+		for(UINT i = 0, l = m_vTimeout.size(); i < l; ++i)
+		{
+			t = &m_vTimeout[i];
+			if(t->status == TS_WAIT)
+			{
+				t->fNextTime += tDelta;
+				t->fStartTime += tDelta;
+			}
+		}
+
+		for(UINT i = 0, l = m_vInterval.size(); i < l; ++i)
+		{
+			t = &m_vInterval[i];
+			if(t->status == TS_WAIT)
+			{
+				t->fNextTime += tDelta;
+				t->fStartTime += tDelta;
+			}
+		}
+	}
+	else
+	{
+		m_tStopPoint = tNow;
+	}
+}
+
+CBaseline* CEntityManager::createBaseline(ID id)
+{
+	if(!ID_VALID(id))
+	{
+		id = m_aBaselines.size();
+	}
+	else
+	{
+		if((UINT)id < m_aBaselines.size())
+		{
+			mem_delete(m_aBaselines[id]);
+		}
+		else
+		{
+			m_aBaselines.reserve(id + 1);
+			for(int i = m_aBaselines.size(); i < id + 1; ++i)
+			{
+				m_aBaselines[i] = 0;
+			}
+		}
+
+	}
+	CBaseline *pBaseline = m_aBaselines[id] = new CBaseline(id);
+
+	CBaseEntity *pEnt;
+	proptable_t * pTbl;
+	char buf[4096];
+	EntDefaultsMap *pDefaults;
+	const EntDefaultsMap::Node *pNode;
+
+	for(int i = 0, l = m_vEntList.size(); i < l; ++i)
+	{
+		pEnt = m_vEntList[i];
+		if(pEnt && (pEnt->getFlags() & EF_LEVEL) && !(pEnt->getFlags() & EF_REMOVED))
+		{
+			CBaseline::ent_record_s record;
+			record.m_id = pEnt->getId();
+			record.m_sClassname = pEnt->getClassName();
+
+			pDefaults = CEntityFactoryMap::GetInstance()->getDefaults(pEnt->getClassName());
+			pTbl = CEntityFactoryMap::GetInstance()->getPropTable(pEnt->getClassName());
+			do
+			{
+				for(int j = 0; j < pTbl->numFields; ++j)
+				{
+					if(pTbl->pData[j].szKey && !(pTbl->pData[j].flags & PDFF_INPUT) && !record.m_mProps.KeyExists(pTbl->pData[j].szKey))
+					{
+						pEnt->getKV(pTbl->pData[j].szKey, buf, sizeof(buf));
+						//test defaults
+						if(!pDefaults->KeyExists(pTbl->pData[j].szKey, &pNode) || strcmp(*(pNode->Val), buf))
+						{
+							record.m_mProps[pTbl->pData[j].szKey] = buf;
+						}
+					}
+				}
+			}
+			while((pTbl = pTbl->pBaseProptable));
+
+			pBaseline->m_aRecords.push_back(record);
+		}
+	}
+	return(pBaseline);
+}
+
+CBaseline* CEntityManager::getBaseline(ID id)
+{
+	if(!ID_VALID(id) || (UINT)id >= m_aBaselines.size())
+	{
+		return(NULL);
+	}
+
+	return(m_aBaselines[id]);
+}
+
+void CEntityManager::dispatchBaseline(CBaseline *pBaseline)
+{
+	CBaseEntity *pEnt;
+	unloadObjLevel();
+	finalRemove();
+	//m_vFreeIDs.clearFast();
+	//m_vEntList.resize(RESERVED_SLOTS);
+
+	if(!pBaseline->m_aRecords.size())
+	{
+		return;
+	}
+
+	UINT idMax = pBaseline->m_aRecords[pBaseline->m_aRecords.size() - 1].m_id;
+	if(m_vEntList.size() <= idMax)
+	{
+		m_vEntList.reserve(idMax + 1);
+	}
+
+	const AssotiativeArray<String, String>::Node *pNode;
+	for(int i = 0, l = pBaseline->m_aRecords.size(); i < l; ++i)
+	{
+		CBaseline::ent_record_s *pRecord = &pBaseline->m_aRecords[i];
+
+		if(!(pEnt = CREATE_ENTITY_NOPOST(pRecord->m_sClassname.c_str(), this)))
+		{
+			printf(COLOR_LRED "Unable to load entity #%d, classname '%s' undefined\n" COLOR_RESET, i, pRecord->m_sClassname.c_str());
+			continue;
+		}
+
+		if(pEnt->getId() != pRecord->m_id)
+		{
+			m_vEntList[pEnt->getId()] = NULL;
+			m_vFreeIDs.push_back(pEnt->getId());
+
+			for(UINT j = m_vEntList.size(); j < pRecord->m_id; ++j)
+			{
+				m_vEntList[j] = NULL;
+				m_vFreeIDs.push_back(j);
+			}
+			if(pRecord->m_id < m_vEntList.size() && m_vEntList[pRecord->m_id])
+			{
+				ID newId = m_vEntList.size();
+				if(m_vFreeIDs.size())
+				{
+					newId = m_vFreeIDs[m_vFreeIDs.size() - 1];
+					m_vFreeIDs.erase(m_vFreeIDs.size() - 1);
+				}
+				m_vEntList[newId] = m_vEntList[pRecord->m_id];
+				m_vEntList[newId]->m_iId = newId;
+			}
+			else
+			{
+				int idx = m_vFreeIDs.indexOf(pRecord->m_id);
+				if(idx >= 0)
+				{
+					m_vFreeIDs.erase(idx);
+				}
+			}
+			m_vEntList[pRecord->m_id] = pEnt;
+			pEnt->m_iId = pRecord->m_id;
+		}
+
+		if(pRecord->m_mProps.KeyExists("name", &pNode))
+		{
+			pEnt->setKV("name", pNode->Val->c_str());
+		}
+		if(pRecord->m_mProps.KeyExists("origin", &pNode))
+		{
+			pEnt->setKV("origin", pNode->Val->c_str());
+		}
+		if(pRecord->m_mProps.KeyExists("rotation", &pNode))
+		{
+			pEnt->setKV("rotation", pNode->Val->c_str());
+		}
+
+		pEnt->setFlags(pEnt->getFlags() | EF_LEVEL);
+	}
+
+	const char *key;
+	for(int i = 0, l = pBaseline->m_aRecords.size(); i < l; ++i)
+	{
+		CBaseline::ent_record_s *pRecord = &pBaseline->m_aRecords[i];
+
+		pEnt = m_vEntList[pRecord->m_id];
+		for(AssotiativeArray<String, String>::Iterator it = pRecord->m_mProps.begin(); it; it++)
+		{
+			key = it.first->c_str();
+
+			if(strcmp(key, "origin") && strcmp(key, "name") && strcmp(key, "rotation"))
+			{
+				pEnt->setKV(key, it.second->c_str());
+			}
+		}
+	}
+
+	for(int i = 1, l = m_vEntList.size(); i < l; ++i)
+	{
+		pEnt = m_vEntList[i];
+		if(pEnt)
+		{
+			pEnt->onPostLoad();
+		}
+	}
+}
+
+#if 0
+void CEntityManager::serializeBaseline(CBaseline *pBaseline, INETbuff *pBuf)
+{
+	CBaseline::ent_record_s *pRecord;
+	const char *key;
+
+	pBuf->writeChar(pBaseline->m_aRecords.size());
+	for(int i = 0, l = pBaseline->m_aRecords.size(); i < l; ++i)
+	{
+		pRecord = &pBaseline->m_aRecords[i];
+		pBuf->writeChar(pRecord->m_id);
+		pBuf->writeString(pRecord->m_sClassname.c_str());
+
+		pBuf->writeChar(pRecord->m_mProps.Size());
+		for(AssotiativeArray<String, String>::Iterator it = pRecord->m_mProps.begin(); it; it++)
+		{
+			pBuf->writeString(it.first->c_str());
+			pBuf->writeString(it.second->c_str());
+		}
+	}
+}
+
+CBaseline *CEntityManager::deserializeBaseline(ID id, INETbuff *pBuf)
+{
+	assert(ID_VALID(id));
+
+	if(id < m_aBaselines.size())
+	{
+		mem_delete(m_aBaselines[id]);
+	}
+	else
+	{
+		m_aBaselines.reserve(id + 1);
+		for(int i = m_aBaselines.size(); i < id + 1; ++i)
+		{
+			m_aBaselines[i] = 0;
+		}
+	}
+	CBaseline *pBaseline = m_aBaselines[id] = new CBaseline(id);
+
+	UINT uCount = pBuf->readChar();
+	char buf[4096], buf2[32];
+
+	for(UINT i = 0; i < uCount; ++i)
+	{
+		CBaseline::ent_record_s record;
+		record.m_id = pBuf->readChar();
+		pBuf->readString(buf, sizeof(buf));
+		record.m_sClassname = buf;
+
+		//printf("Ent: %s\n", buf);
+
+		UINT uPropCount = pBuf->readChar();
+		for(UINT j = 0; j < uPropCount; ++j)
+		{
+			pBuf->readString(buf2, sizeof(buf2));
+			pBuf->readString(buf, sizeof(buf));
+			//printf("    %s: \"%s\"\n", buf2, buf);
+			record.m_mProps[buf2] = buf;
+		}
+		//printf("\n");
+
+		pBaseline->m_aRecords.push_back(record);
+	}
+
+	return(pBaseline);
+}
+#endif
diff --git a/source/game/EntityManager.h b/source/game/EntityManager.h
index 9ae3a173834b4f00cd5f6ffcccefaf3f1e1b3b23..03cd994d3df1453b7708ab743569c6cf8f2e90bd 100644
--- a/source/game/EntityManager.h
+++ b/source/game/EntityManager.h
@@ -12,7 +12,8 @@ See the license in LICENSE
 #include <gdefines.h>
 #include <common/array.h>
 #include <chrono>
-#include <mutex>
+#include <xcommon/XEvents.h>
+#include <xcommon/IXCore.h>
 
 #include "proptable.h"
 
@@ -67,8 +68,25 @@ struct timeout_output_t
 	inputdata_t data;
 };
 
+class CBaseline;
+
 class CEntityManager
 {
+	class CCVarEventListener: public IEventListener<XEventCvarChanged>
+	{
+	public:
+		CCVarEventListener(CEntityManager *pMgr, IXCore *pCore):
+			m_pMgr(pMgr),
+			m_pCore(pCore)
+		{}
+
+		void onEvent(const XEventCvarChanged *pEvent) override;
+
+	private:
+		CEntityManager *m_pMgr;
+		IXCore *m_pCore;
+	};
+
 	friend class CBaseEntity;
 	friend class CEntityFactoryMap;
 public:
@@ -113,6 +131,17 @@ public:
 	void setEditorMode(bool isEditor = true);
 	bool isEditorMode();
 
+	void setTimeRunning(bool set);
+
+	CBaseline* createBaseline(ID forceId = -1);
+	CBaseline* getBaseline(ID id);
+	void dispatchBaseline(CBaseline *pBaseline);
+
+#if 0
+	void serializeBaseline(CBaseline *pBaseline, INETbuff *pBuf);
+	CBaseline *deserializeBaseline(ID id, INETbuff *pBuf);
+#endif
+
 protected:
 	ID reg(CBaseEntity *pEnt);
 	void unreg(ID ent);
@@ -131,16 +160,25 @@ protected:
 	Array<ID> m_vFreeInterval;
 	Array<ID> m_vFreeTimeout;
 
-	int m_iThreadNum;
+	int m_iThreadNum = 1;
 
 	//! @warning это нужно хранить в течение работы проги, т.к. таблицы дефолтов ссылаются напрямую на этот объект
-	ISXConfig *m_pDefaultsConf;
-	ISXConfig *m_pDynClassConf;
+	ISXConfig *m_pDefaultsConf = NULL;
+	ISXConfig *m_pDynClassConf = NULL;
 
 	bool m_isEditorMode = false;
 
-	std::mutex m_mxTimeout;
-	std::mutex m_mxInterval;
+	SpinLock m_mxTimeout;
+	SpinLock m_mxInterval;
+
+	bool m_isTimeRunning = false;
+	time_point m_tStopPoint = std::chrono::high_resolution_clock::now();
+	CCVarEventListener m_cvarListener;
+
+	Array<CBaseline*> m_aBaselines;
+
+private:
+	void finalRemove();
 };
 
 #endif
diff --git a/source/game/GameData.cpp b/source/game/GameData.cpp
index 7efb990990460a026b4050e7816afd278ca2dce7..75df4dc378ee72850ebfc26a183badac4c428e3d 100644
--- a/source/game/GameData.cpp
+++ b/source/game/GameData.cpp
@@ -639,6 +639,9 @@ GameData::GameData(HWND hWnd, bool isGame):
 	Core_0RegisterCVarFloat("cl_mousesense", 2.0f, "Mouse sense value");
 	Core_0RegisterCVarBool("cl_invert_y", false, "Invert Y axis");
 
+	Core_0RegisterCVarBool("dev_reset_world_on_run", false, "Reset world on level run");
+		
+
 	Core_0RegisterCVarBool("cl_bob", true, "View bobbing");
 	Core_0RegisterCVarFloat("cl_bob_walk_y", 0.1f, "View bobbing walk y amplitude");
 	Core_0RegisterCVarFloat("cl_bob_walk_x", 0.1f, "View bobbing walk strafe amplitude");
diff --git a/source/game/GameStates.cpp b/source/game/GameStates.cpp
index 4241ef43b875ce9794ce95b8eefaef06a0aae40a..a91c98c4289737c694d099ad36d10459f31d9d5b 100644
--- a/source/game/GameStates.cpp
+++ b/source/game/GameStates.cpp
@@ -1,6 +1,7 @@
 #include "GameStates.h"
 
 #include "GameData.h"
+#include "Baseline.h"
 
 #include <score/sxscore.h>
 #include <input/sxinput.h>
@@ -25,6 +26,7 @@ CMainMenuGameState::~CMainMenuGameState()
 
 void CMainMenuGameState::activate()
 {
+	printf("CMainMenuGameState::activate()\n");
 	GameData::m_pGUIStack->setActiveDesktop(m_pDesktop);
 
 	if(GameData::m_pGuiLayer)
@@ -40,6 +42,7 @@ void CMainMenuGameState::activate()
 
 void CMainMenuGameState::deactivate()
 {
+	printf("CMainMenuGameState::deactivate()\n");
 	if(GameData::m_pGuiLayer)
 	{
 		GameData::m_pGuiLayer->play(false);
@@ -72,6 +75,7 @@ CIngameMenuGameState::~CIngameMenuGameState()
 
 void CIngameMenuGameState::activate()
 {
+	printf("CIngameMenuGameState::activate()\n");
 	GameData::m_pGUIStack->setActiveDesktop(m_pDesktop);
 
 	if(GameData::m_pGuiLayer)
@@ -87,6 +91,7 @@ void CIngameMenuGameState::activate()
 
 void CIngameMenuGameState::deactivate()
 {
+	printf("CIngameMenuGameState::deactivate()\n");
 	GameData::m_pGUIStack->setActiveDesktop(m_pDesktop);
 
 	if(GameData::m_pGuiLayer)
@@ -104,6 +109,14 @@ void CIngameMenuGameState::deactivate()
 
 void CIngameGameState::activate()
 {
+	printf("CIngameGameState::activate()\n");
+	static const bool *dev_reset_world_on_run = GET_PCVAR_BOOL("dev_reset_world_on_run");
+
+	if(*dev_reset_world_on_run)
+	{
+		m_pBaseLine = GameData::m_pMgr->createBaseline(m_pBaseLine ? m_pBaseLine->getId() : -1);
+	}
+
 	Core_0ConsoleExecCmd("cl_grab_cursor 1");
 	GameData::m_pHUDcontroller->activate();
 
@@ -113,6 +126,8 @@ void CIngameGameState::activate()
 	SSInput_SetEnable(true);
 	SPhysics_EnableSimulation();
 
+	Core_0ConsoleExecCmd("game_time_running 1");
+
 	if(GameData::m_pGameLayer)
 	{
 		GameData::m_pGameLayer->play(true);
@@ -121,6 +136,11 @@ void CIngameGameState::activate()
 
 void CIngameGameState::deactivate()
 {
+	printf("CIngameGameState::deactivate()\n");
+	static const bool *dev_reset_world_on_run = GET_PCVAR_BOOL("dev_reset_world_on_run");
+
+	Core_0ConsoleExecCmd("game_time_running 0");
+
 	ID idTimerRender = Core_RIntGet(G_RI_INT_TIMER_GAME);
 	Core_TimeWorkingSet(idTimerRender, false);
 	SSCore_ChannelStop(SX_SOUND_CHANNEL_GAME);
@@ -131,14 +151,20 @@ void CIngameGameState::deactivate()
 	{
 		GameData::m_pGameLayer->play(false);
 	}
+
+	if(*dev_reset_world_on_run)
+	{
+		GameData::m_pMgr->dispatchBaseline(m_pBaseLine);
+	}
 }
 
 //##########################################################################
 
 void CEditorState::activate()
 {
-	ID idTimerRender = Core_RIntGet(G_RI_INT_TIMER_GAME);
-	Core_TimeWorkingSet(idTimerRender, true);
+	printf("CEditorState::activate()\n");
+//	ID idTimerRender = Core_RIntGet(G_RI_INT_TIMER_GAME);
+//	Core_TimeWorkingSet(idTimerRender, true);
 	//SSCore_ChannelPlay(SX_SOUND_CHANNEL_GAME);
 //	SSInput_SetEnable(true);
 
@@ -147,10 +173,11 @@ void CEditorState::activate()
 
 void CEditorState::deactivate()
 {
+	printf("CEditorState::deactivate()\n");
 	GameData::m_pMgr->setEditorMode(false);
 
-	ID idTimerRender = Core_RIntGet(G_RI_INT_TIMER_GAME);
-	Core_TimeWorkingSet(idTimerRender, false);
+//	ID idTimerRender = Core_RIntGet(G_RI_INT_TIMER_GAME);
+//	Core_TimeWorkingSet(idTimerRender, false);
 	//SSCore_ChannelStop(SX_SOUND_CHANNEL_GAME);
 //	SSInput_SetEnable(false);
 }
diff --git a/source/game/GameStates.h b/source/game/GameStates.h
index 4e23299023c89d96b6d5fe73ccdd6329911d91cc..027048b79de7a96b254fcb2ce43681d9a212bc79 100644
--- a/source/game/GameStates.h
+++ b/source/game/GameStates.h
@@ -4,6 +4,8 @@
 #include "IGameState.h"
 #include <gui/guimain.h>
 
+class CBaseline;
+
 class CMainMenuGameState final: public IGameState
 {
 public:
@@ -28,18 +30,21 @@ protected:
 	gui::IDesktop *m_pDesktop;
 };
 
-class CIngameGameState: public IGameState
+class CIngameGameState final: public IGameState
 {
 public:
-	void activate();
-	void deactivate();
+	void activate() override;
+	void deactivate() override;
+
+private:
+	CBaseline *m_pBaseLine = NULL;
 };
 
-class CEditorState: public IGameState
+class CEditorState final: public IGameState
 {
 public:
-	void activate();
-	void deactivate();
+	void activate() override;
+	void deactivate() override;
 };
 
 #endif
diff --git a/source/light/LightSystem.cpp b/source/light/LightSystem.cpp
index c31c81bdc2bb674dace547c6413192d097e2f6fd..85c8137b5423a50325a4507472f228af5cda0038 100644
--- a/source/light/LightSystem.cpp
+++ b/source/light/LightSystem.cpp
@@ -13,11 +13,17 @@ public:
 	{
 		switch(pEvent->type)
 		{
+		case XEventLevel::TYPE_LOAD_BEGIN:
+			m_pLightSystem->setEnabled(false);
+			break;
+
 		case XEventLevel::TYPE_LOAD_END:
 			XEventLevelSize levelSize;
 			m_pLevelSizeChannel->broadcastEvent(&levelSize);
 
 			m_pLightSystem->setLevelSize(levelSize.vMin, levelSize.vMax);
+
+			m_pLightSystem->setEnabled(true);
 			break;
 		}
 	}
@@ -413,6 +419,11 @@ void XMETHODCALLTYPE CLightSystem::updateVisibility()
 {
 	assert(m_pMainCamera);
 
+	if(!m_isEnabled)
+	{
+		return;
+	}
+
 	float3 vCamPos;
 	m_pMainCamera->getPosition(&vCamPos);
 	float3 vCamDir;
@@ -492,6 +503,11 @@ void XMETHODCALLTYPE CLightSystem::setRenderPipeline(IXRenderPipeline *pRenderPi
 
 void XMETHODCALLTYPE CLightSystem::renderGI(IGXTexture2D *pLightTotal, IGXTexture2D *pTempBuffer)
 {
+	if(!m_isEnabled)
+	{
+		return;
+	}
+
 	IGXContext *pCtx = m_pDevice->getThreadContext();
 
 	IGXDepthStencilSurface *pOldDSSurface = pCtx->getDepthStencilSurface();
diff --git a/source/light/LightSystem.h b/source/light/LightSystem.h
index 1be91aa3eccd91e8864e3badfac99c18f175a1fd..bfd88491d1a748060722ee414cca173f8a35218c 100644
--- a/source/light/LightSystem.h
+++ b/source/light/LightSystem.h
@@ -47,6 +47,11 @@ public:
 	void XMETHODCALLTYPE renderToneMapping(IGXTexture2D *pSourceLight) override;
 	void XMETHODCALLTYPE renderDebug() override;
 
+	void setEnabled(bool set)
+	{
+		m_isEnabled = set;
+	}
+
 protected:
 	void showGICubes();
 
@@ -175,6 +180,8 @@ protected:
 	IGXTexture2D *m_pLightLuminance1 = NULL;
 	IGXTexture2D *m_pAdaptedLuminance[2];
 	UINT m_uCurrAdaptedLuminanceTarget = 0;
+
+	bool m_isEnabled = true;
 };
 
 #endif
diff --git a/source/terrax/mainWindow.cpp b/source/terrax/mainWindow.cpp
index 4107d9ed22535d9ab76ea382c3dc4e031cb23717..1a70c5e5e2d11ca1c3a3bf6f93c529c086141801 100644
--- a/source/terrax/mainWindow.cpp
+++ b/source/terrax/mainWindow.cpp
@@ -1307,6 +1307,10 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
 		case ID_FILE_EXIT:
 			PostMessage(hWnd, WM_CLOSE, 0, 0);
 			break;
+
+		case ID_LEVEL_RUN:
+			g_pEngine->getCore()->getConsole()->execCommand("run_level");
+			break;
 		}
 		break;
 
diff --git a/source/terrax/resource.h b/source/terrax/resource.h
index 517c8900907cc0402bb88d5709184c79f2854b50..0598f6e2c8ac8743f096b5b4d4d612c1a66af56f 100644
Binary files a/source/terrax/resource.h and b/source/terrax/resource.h differ
diff --git a/source/terrax/terrax.cpp b/source/terrax/terrax.cpp
index 26b466eb8d510e7807954cfc08971483a8cb2cc0..7254332892f6cbe2c14fde2ff3531ccc7bcf48d5 100644
--- a/source/terrax/terrax.cpp
+++ b/source/terrax/terrax.cpp
@@ -83,8 +83,70 @@ class CEngineCallback: public IXEngineCallback
 public:
 	void XMETHODCALLTYPE onGraphicsResize(UINT uWidth, UINT uHeight, bool isFullscreen, bool isBorderless, IXEngine *pEngine) override
 	{
-		XReleaseViewports();
-		XInitViewports();
+		static const bool *terrax_detach_3d = NULL;
+		if(!terrax_detach_3d && m_pCore)
+		{
+			terrax_detach_3d = m_pCore->getConsole()->getPCVarBool("terrax_detach_3d");
+		}
+
+		if(terrax_detach_3d && *terrax_detach_3d)
+		{
+			RECT rc = {0, 0, (int)uWidth, (int)uHeight};
+
+			HMONITOR monitor = MonitorFromWindow(g_hTopLeftWnd, MONITOR_DEFAULTTONEAREST);
+			MONITORINFO info;
+			info.cbSize = sizeof(MONITORINFO);
+			GetMonitorInfo(monitor, &info);
+			bool bForceNoBorder = (rc.right - rc.left == info.rcMonitor.right - info.rcMonitor.left
+				&& rc.bottom - rc.top == info.rcMonitor.bottom - info.rcMonitor.top);
+
+
+			SetClassLong(g_hTopLeftWnd, GCL_STYLE, GetClassLong(g_hTopLeftWnd, GCL_STYLE) | CS_NOCLOSE);
+
+			DWORD wndStyle = GetWindowLong(g_hTopLeftWnd, GWL_STYLE) | WS_MINIMIZEBOX | WS_SYSMENU;
+
+			if(isFullscreen || isBorderless || bForceNoBorder)
+			{
+				wndStyle &= ~(WS_SYSMENU | WS_CAPTION | WS_MAXIMIZE | WS_MINIMIZE | WS_THICKFRAME);
+			}
+			else
+			{
+				wndStyle |= WS_CAPTION | WS_THICKFRAME;
+				wndStyle &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
+			}
+
+			SetWindowLong(g_hTopLeftWnd, GWL_STYLE, wndStyle);
+
+
+			AdjustWindowRect(&rc, wndStyle, false);
+
+			UINT uSWPflags = SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOOWNERZORDER;
+
+
+			int iPosX = 0;
+			int iPosY = 0;
+			if(!(isFullscreen || isBorderless))
+			{
+				iPosX = (GetSystemMetrics(SM_CXSCREEN) - (rc.right - rc.left)) / 2;
+				iPosY = (GetSystemMetrics(SM_CYSCREEN) - (rc.bottom - rc.top)) / 2;
+			}
+			RECT rcOld;
+			GetWindowRect(g_hTopLeftWnd, &rcOld);
+
+
+			if(rcOld.right - rcOld.left != rc.right - rc.left || rcOld.bottom - rcOld.top != rc.bottom - rc.top)
+			{
+				uSWPflags &= ~SWP_NOSIZE;
+				//SetWindowPos(m_hWnd, HWND_TOP, 0, 0, rc.right - rc.left, rc.bottom - rc.top, SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER);
+			}
+
+			SetWindowPos(g_hTopLeftWnd, HWND_TOP, iPosX, iPosY, rc.right - rc.left, rc.bottom - rc.top, uSWPflags);
+		}
+		else
+		{
+			XReleaseViewports();
+			XInitViewports();
+		}
 	}
 
 	bool XMETHODCALLTYPE processWindowMessages() override
@@ -236,10 +298,22 @@ public:
 
 	ICamera* XMETHODCALLTYPE getCameraForFrame() override
 	{
+		static const bool *terrax_detach_3d = m_pCore->getConsole()->getPCVarBool("terrax_detach_3d");
+		if(*terrax_detach_3d)
+		{
+			return(SGame_GetActiveCamera());
+		}
+		
 		return(g_xConfig.m_pViewportCamera[XWP_TOP_LEFT]);
 	}
 
+	void setCore(IXCore *pCore)
+	{
+		m_pCore = pCore;
+	}
+
 protected:
+	IXCore *m_pCore = NULL;
 };
 
 class CRenderPipeline: public IXUnknownImplementation<IXRenderPipeline>
@@ -284,6 +358,13 @@ public:
 	void renderFrame(float fDeltaTime) override
 	{
 		m_pOldPipeline->renderFrame(fDeltaTime);
+
+		static const bool *terrax_detach_3d = m_pCore->getConsole()->getPCVarBool("terrax_detach_3d");
+
+		if(*terrax_detach_3d)
+		{
+			return;
+		}
 		
 		IGXContext *pDXDevice = getDevice()->getThreadContext();
 		pDXDevice->setDepthStencilState(g_pDSDefault);
@@ -384,8 +465,22 @@ public:
 	{
 		m_pOldPipeline->updateVisibility();
 
+		static const bool *terrax_detach_3d = m_pCore->getConsole()->getPCVarBool("terrax_detach_3d");
+
+		if(*terrax_detach_3d)
+		{
+			return;
+		}
+
+		HWND hWnds[] = {g_hTopRightWnd, g_hBottomLeftWnd, g_hBottomRightWnd};
+
 		for(UINT i = 0; i < 3; ++i)
 		{
+			if(!IsWindowVisible(hWnds[i]))
+			{
+				continue;
+			}
+
 			m_pCameraVisibility[i + 1]->updateForCamera(g_xConfig.m_pViewportCamera[i + 1]);
 		}
 	}
@@ -449,6 +544,55 @@ public:
 	IXRenderableVisibility *m_pCameraVisibility[4];
 };
 
+class CCVarEventListener: public IEventListener<XEventCvarChanged>
+{
+public:
+	CCVarEventListener(IXCore *pCore):
+		m_pCore(pCore)
+	{}
+	void onEvent(const XEventCvarChanged *pEvent) override
+	{
+		static const bool *terrax_detach_3d = m_pCore->getConsole()->getPCVarBool("terrax_detach_3d");
+
+		if(terrax_detach_3d == pEvent->pCvar)
+		{
+			if(*terrax_detach_3d)
+			{
+				ShowWindow(g_hTopLeftWnd, SW_HIDE);
+				m_lOldStyle = GetWindowLongA(g_hTopLeftWnd, GWL_STYLE);
+				SetParent(g_hTopLeftWnd, NULL);
+				SetWindowLongA(g_hTopLeftWnd, GWL_STYLE, WS_OVERLAPPED | WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
+				SetWindowTextA(g_hTopLeftWnd, "SkyXEngine");
+
+				m_lOldExStyle = GetWindowLongA(g_hTopLeftWnd, GWL_EXSTYLE);
+				SetWindowLongA(g_hTopLeftWnd, GWL_EXSTYLE, 0);
+
+				SetWindowPos(g_hTopLeftWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE);
+
+				EnableWindow(g_hWndMain, FALSE);
+			}
+			else
+			{
+				EnableWindow(g_hWndMain, TRUE);
+
+				ShowWindow(g_hTopLeftWnd, SW_HIDE);
+				SetParent(g_hTopLeftWnd, g_hWndMain);
+				SetWindowLongA(g_hTopLeftWnd, GWL_STYLE, m_lOldStyle);
+				SetWindowLongA(g_hTopLeftWnd, GWL_EXSTYLE, m_lOldExStyle);
+				SetWindowPos(g_hTopLeftWnd, HWND_TOP, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE);
+				PostMessage(g_hWndMain, WM_SIZE, 0, 0);
+
+				ShowCursor(TRUE);
+			}
+		}
+	}
+
+private:
+	IXCore *m_pCore;
+	LONG m_lOldStyle;
+	LONG m_lOldExStyle;
+};
+
 #if defined(_WINDOWS)
 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
 {
@@ -481,11 +625,17 @@ int main(int argc, char **argv)
 	g_pEngine = pEngine;
 	CEngineCallback engineCb;
 	pEngine->initGraphics((XWINDOW_OS_HANDLE)g_hTopLeftWnd, &engineCb);
+	engineCb.setCore(pEngine->getCore());
+	pEngine->getCore()->getConsole()->registerCVar("terrax_detach_3d", false, "", FCVAR_NOTIFY);
 	pEngine->getCore()->getConsole()->execCommand("gmode editor");
 	pEngine->getCore()->getConsole()->execCommand("exec ../config_editor.cfg");
 	CRenderPipeline *pPipeline = new CRenderPipeline(Core_GetIXCore());
 	XInitGuiWindow(false);
 
+	CCVarEventListener cvarListener(pEngine->getCore());
+	auto *pChannel = pEngine->getCore()->getEventChannel<XEventCvarChanged>(EVENT_CVAR_CHANGED_GUID);
+	pChannel->addListener(&cvarListener);
+
 	RECT rcTopLeft;
 	GetClientRect(g_hTopLeftWnd, &rcTopLeft);
 
@@ -890,6 +1040,8 @@ int main(int argc, char **argv)
 	}
 	XReleaseViewports();
 
+	pChannel->removeListener(&cvarListener);
+
 	mem_release(g_pDashedMaterial);
 	mem_release(g_pDSNoZ);
 	mem_release(g_pDSDefault);
diff --git a/source/terrax/terrax.rc b/source/terrax/terrax.rc
index 5d28e5830d0cfdb2425ede311dd3c18348908b04..01c8a740f5570fc68ad10939428f6ea57e0a2acb 100644
Binary files a/source/terrax/terrax.rc and b/source/terrax/terrax.rc differ