diff --git a/source/core/menu/savegamemanager.cpp b/source/core/menu/savegamemanager.cpp
index 393794f19..8fc642e16 100644
--- a/source/core/menu/savegamemanager.cpp
+++ b/source/core/menu/savegamemanager.cpp
@@ -66,8 +66,16 @@ void FSavegameManager::LoadGame(FSaveGameNode* node)
 	{
 		if (gi->LoadGame(node))
 		{
-			// do something here?
+			gamestate = GS_LEVEL;
 		}
+		else
+		{
+			I_Error("%s: Failed to load savegame", node->Filename.GetChars());
+		}
+	}
+	else
+	{
+		I_Error("%s: Failed to open savegame", node->Filename.GetChars());
 	}
 }
 
diff --git a/source/exhumed/src/d_menu.cpp b/source/exhumed/src/d_menu.cpp
index 62e855813..5ad385ba0 100644
--- a/source/exhumed/src/d_menu.cpp
+++ b/source/exhumed/src/d_menu.cpp
@@ -28,6 +28,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 #include "version.h"
 #include "raze_sound.h"
 #include "gamestate.h"
+#include "mapinfo.h"
+#include "gamecontrol.h"
 
 
 #include "menu/menu.h"	// to override the local menu.h
@@ -151,9 +153,7 @@ void GameInterface::MenuSound(EMenuSounds snd)
 
 void GameInterface::QuitToTitle()
 {
-	M_StartControlPanel(false);
-	M_SetMenu(NAME_Mainmenu);
-	gamestate = GS_MENUSCREEN;
+	gameaction = ga_mainmenu;
 }
 
 void GameInterface::MenuClosed()
@@ -164,7 +164,8 @@ void GameInterface::MenuClosed()
 
 void GameInterface::StartGame(FNewGameStartup& gs)
 {
-	GameAction = gs.Episode;	// 0 is training, 1 is the regular game
+	auto map = FindMapByLevelNum(gs.Episode);
+	DeferedStartGame(map, 0);	// 0 is training, 1 is the regular game - the game does not have skill levels.
 }
 
 FSavegameInfo GameInterface::GetSaveSig()
diff --git a/source/exhumed/src/exhumed.cpp b/source/exhumed/src/exhumed.cpp
index 3fdc282b1..213d4595d 100644
--- a/source/exhumed/src/exhumed.cpp
+++ b/source/exhumed/src/exhumed.cpp
@@ -63,7 +63,7 @@ int32_t registerosdcommands(void);
 void InitFonts();
 void InitCheats();
 
-int EndLevel = false;
+int EndLevel = 0;
 
 
 InputPacket localInput;
@@ -260,8 +260,6 @@ double calc_smoothratio()
     return I_GetTimeFrac() * MaxSmoothRatio;
 }
 
-void CheckProgression();
-
 void GameMove(void)
 {
     FixPalette();
@@ -336,7 +334,12 @@ static int SelectAltWeapon(int weap2)
 
 void GameInterface::Ticker()
 {
-    if (!paused)
+
+	if (paused)
+	{
+		r_NoInterpolate = true;
+	}
+    else if (EndLevel == 0)
     {
         nPlayerDAng += localInput.q16avel;
         inita &= kAngleMask;
@@ -453,8 +456,17 @@ void GameInterface::Ticker()
         GameMove();
         r_NoInterpolate = false;
     }
-    else r_NoInterpolate = true;
-    CheckProgression(); // todo: Get rid of this.
+	else
+	{
+		// Wait for the end of level sound to play out, but stop updating the playsim.
+		EndLevel--;
+		r_NoInterpolate = false;
+		if (EndLevel == 0)
+		{
+			auto map = currentLevel->levelNumber == 20? nullptr : FindMapByLevelNum(currentLevel->levelNumber+1); // todo: Use the map record for progression
+			CompleteLevel(map);
+		}
+	}
 }
 
 void ExitGame()
diff --git a/source/exhumed/src/exhumed.h b/source/exhumed/src/exhumed.h
index 349d76b56..01c0f5fc6 100644
--- a/source/exhumed/src/exhumed.h
+++ b/source/exhumed/src/exhumed.h
@@ -100,7 +100,6 @@ extern int32_t g_noSetup;
 
 extern char sHollyStr[];
 extern int selectedlevelnew;
-extern int GameAction;
 
 extern int nNetPlayerCount;
 
@@ -254,6 +253,10 @@ struct GameInterface : ::GameInterface
     void GetInput(InputPacket* packet) override;
     void Startup() override;
     const char* GenericCheat(int player, int cheat) override;
+	void NewGame(MapRecord *map, int skill) override;
+	void LevelCompleted(MapRecord *map, int skill) override;
+	void NextLevel(MapRecord *map, int skill) override;
+
 
 	::GameStats getStats() override;
 };
diff --git a/source/exhumed/src/gameloop.cpp b/source/exhumed/src/gameloop.cpp
index 2339f833a..feaaf496f 100644
--- a/source/exhumed/src/gameloop.cpp
+++ b/source/exhumed/src/gameloop.cpp
@@ -50,13 +50,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 BEGIN_PS_NS
 
-void CheckProgression();
-
 short nBestLevel;
 static int32_t nonsharedtimer;
 
-int GameAction=-1;
-
 extern uint8_t nCinemaSeen;
 
 void RunCinemaScene(int num);
@@ -65,14 +61,8 @@ void DrawClock();
 double calc_smoothratio();
 void DoTitle(CompletionFunc completion);
 
-static int FinishLevel(TArray<JobDesc> &jobs)
+static void FinishLevel(int lnum, TArray<JobDesc> &jobs)
 {
-    int lnum = currentLevel->levelNumber;
-    if (lnum > nBestLevel) {
-        nBestLevel = lnum - 1;
-    }
-
-
     StopAllSounds();
 
     bCamera = false;
@@ -81,7 +71,7 @@ static int FinishLevel(TArray<JobDesc> &jobs)
     STAT_Update(lnum == kMap20);
     if (lnum != kMap20)
     {
-        if (EndLevel != 2 && !netgame)
+        if (EndLevel == 13 && !netgame)
         {
             // There's really no choice but to enter an active wait loop here to make the sound play out.
             PlayLocalSound(StaticSound[59], 0, true, CHANF_UI);
@@ -97,7 +87,6 @@ static int FinishLevel(TArray<JobDesc> &jobs)
     else nPlayerLives[0] = 0;
 
     DoAfterCinemaScene(lnum, jobs);
-    return lnum == kMap20? -1 : lnum + 1;
 }
 
 
@@ -145,7 +134,6 @@ void GameInterface::Render()
 // 
 //
 //---------------------------------------------------------------------------
-void CheckProgression();
 
 void GameInterface::DrawBackground()
 {
@@ -161,7 +149,6 @@ void GameInterface::DrawBackground()
     // draw the fire urn/lamp thingies
     DrawRel(kTile3512 + dword_9AB5F, 50, 150, 32);
     DrawRel(kTile3512 + ((dword_9AB5F + 2) & 3), 270, 150, 32);
-    CheckProgression();
 }
 
 //---------------------------------------------------------------------------
@@ -170,78 +157,73 @@ void GameInterface::DrawBackground()
 //
 //---------------------------------------------------------------------------
 
-void CheckProgression()
+static void Intermission(MapRecord *from_map, MapRecord *to_map)
 {
     TArray<JobDesc> jobs;
-    bool startlevel = false;
-    int mylevelnew = -1;
 
-    if (GameAction >= 0)
-    {
-        if (GameAction < 1000)
-        {
-            // start a new game on the given level
-            currentLevel = nullptr;
-            mylevelnew = GameAction;
-            currentLevel = FindMapByLevelNum(mylevelnew);
-            GameAction = -1;
-            InitNewGame();
-            if (mylevelnew > 0) STAT_StartNewGame("Exhumed", 1);
-            if (mylevelnew != 0) nBestLevel = mylevelnew - 1;
-        }
-        else
-        {
-            // A savegame was loaded. Just start the level without any further actions.
-            GameAction = -1;
-            gamestate = GS_LEVEL;
-            return;
-        }
-    }
-    else if (EndLevel)
-    {
-        if (currentLevel->levelNumber == 0) startmainmenu();
-        else mylevelnew = FinishLevel(jobs);
-        EndLevel = false;
-    }
-    if (mylevelnew > -1 && mylevelnew < kMap20)
-    {
-        startlevel = true;
-        // start a new game at the given level
-        if (!nNetPlayerCount && mylevelnew > 0)
-        {
-            showmap(currentLevel? currentLevel->levelNumber : -1, mylevelnew, nBestLevel, jobs);
-        }
-        else
-            jobs.Push({ Create<DScreenJob>() }); // we need something in here even in the multiplayer case.
-    }
-    if (jobs.Size() > 0)
-    {
-        selectedlevelnew = mylevelnew;
-        RunScreenJob(jobs.Data(), jobs.Size(), [=](bool)
-            {
-                if (!startlevel) gamestate = GS_STARTUP;
-                else
-                {
-                    gamestate = GS_LEVEL;
+	if (to_map && to_map->levelNumber != 0)
+	{
+		nBestLevel = to_map->levelNumber - 1;
+		FinishLevel(to_map->levelNumber, jobs);
+	}
+	
+	if (to_map->levelNumber > -1 && to_map->levelNumber < kMap20)
+	{
+		// start a new game at the given level
+		if (!nNetPlayerCount && to_map->levelNumber > 0)
+		{
+			showmap(from_map? from_map->levelNumber : -1, to_map->levelNumber, nBestLevel, jobs);
+		}
+		else
+			jobs.Push({ Create<DScreenJob>() }); // we need something in here even in the multiplayer case.
+	}
+	if (jobs.Size() > 0)
+	{
+		RunScreenJob(jobs.Data(), jobs.Size(), [=](bool)
+		{
+			if (!to_map) gameaction = ga_startup; // this was the end of the game
+			else
+			{
+				if (to_map->levelNumber != selectedlevelnew)
+				{
+					// User can switch destination on the scrolling map.
+					g_nextmap = FindMapByLevelNum(selectedlevelnew);
+					STAT_Cancel();
+				}
+				gameaction = ga_nextlevel;
 
-                    InitLevel(selectedlevelnew);
-#if 0
-                    // this would be the place to autosave upon level start
-                    if (!bInDemo && selectedlevelnew > nBestLevel && selectedlevelnew != 0 && selectedlevelnew <= kMap20) {
-                        menu_GameSave(SavePosition);
-                    }
-#endif
-                    if (selectedlevelnew > nBestLevel)
-                    {
-                        nBestLevel = selectedlevelnew;
-                    }
+			}
+		});
+	}
+	
+}
 
-                    if (selectedlevelnew == 11) nCinemaSeen |= 2;
-                    if (mylevelnew != selectedlevelnew) STAT_Cancel();
-                    else STAT_NewLevel(currentLevel->labelName);
-                }
-            });
-    }
+void GameInterface::NextLevel(MapRecord *map, int skill)
+{
+	InitLevel(map->levelNumber);
+
+	if (map->levelNumber > nBestLevel)
+	{
+		nBestLevel = selectedlevelnew;
+	}
+	
+	if (map->levelNumber == 11) nCinemaSeen |= 2;
+	STAT_NewLevel(currentLevel->labelName);
+	
+}
+
+void GameInterface::NewGame(MapRecord *map, int skill)
+{
+	// start a new game on the given level
+	InitNewGame();
+	if (map->levelNumber == 1) STAT_StartNewGame("Exhumed", 1);
+	Intermission(nullptr, map);
+}
+
+void GameInterface::LevelCompleted(MapRecord *map, int skill)
+{
+	if (currentLevel->levelNumber == 0) startmainmenu();
+	else Intermission(currentLevel, map);
 }
 
 //---------------------------------------------------------------------------
@@ -253,14 +235,13 @@ void CheckProgression()
 void GameInterface::Startup()
 {
     resettiming();
-    GameAction = -1;
-    EndLevel = false;
+    EndLevel = 0;
 
     if (userConfig.CommandMap.IsNotEmpty())
     {
         /*
         auto map = FindMapByName(userConfig.CommandMap);
-        if (map) GameAction = map->levelNumber;
+        if (map) DeferedStartMap(map, 0);
         userConfig.CommandMap = "";
         goto again;
         */
@@ -276,8 +257,7 @@ void GameInterface::Startup()
 void GameInterface::ErrorCleanup()
 {
     // Clear all progression sensitive variables here.
-    GameAction = -1;
-    EndLevel = false;
+    EndLevel = 0;
 }
 
 END_PS_NS
diff --git a/source/exhumed/src/init.cpp b/source/exhumed/src/init.cpp
index 2102d7e7e..7a09fe99e 100644
--- a/source/exhumed/src/init.cpp
+++ b/source/exhumed/src/init.cpp
@@ -173,7 +173,7 @@ uint8_t LoadLevel(int nMap)
     return true;
 }
 
-void InitLevel(int level)
+void InitLevel(int level) // todo: use a map record
 {
     StopCD();
     if (!LoadLevel(level)) {
@@ -187,7 +187,7 @@ void InitLevel(int level)
         RestartPlayer(i);
         InitPlayerKeys(i);
     }
-
+    EndLevel = 0;
     lastfps = 0;
     InitStatus();
     ResetView();
diff --git a/source/exhumed/src/object.cpp b/source/exhumed/src/object.cpp
index 73183e2d9..dcb987f01 100644
--- a/source/exhumed/src/object.cpp
+++ b/source/exhumed/src/object.cpp
@@ -1544,7 +1544,7 @@ void DoFinale()
             }
             else if (nFinaleStage == 3 && leveltime >= nextstage)
             {
-                EndLevel = true;
+                EndLevel = 13;
             }
         }
         else
diff --git a/source/exhumed/src/osdcmds.cpp b/source/exhumed/src/osdcmds.cpp
index 6c3f8596d..466281c37 100644
--- a/source/exhumed/src/osdcmds.cpp
+++ b/source/exhumed/src/osdcmds.cpp
@@ -35,63 +35,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 BEGIN_PS_NS
 
-
-static int osdcmd_map(CCmdFuncPtr parm)
-{
-    if (parm->numparms != 1)
-    {
-        return CCMD_SHOWHELP;
-    }
-    FString mapname = parm->parms[0];
-    FString mapfilename = mapname;
-    DefaultExtension(mapfilename, ".map");
-
-    if (!fileSystem.FileExists(mapfilename))
-    {
-        Printf(TEXTCOLOR_RED "map: file \"%s\" not found.\n", mapfilename.GetChars());
-        return CCMD_OK;
-    }
-	
-	// Check if the map is already defined.
-    auto map = FindMapByName(mapname);
-    if (map)
-    {
-        GameAction = map->levelNumber;
-    }
-    return CCMD_OK;
-}
-
-static int osdcmd_changelevel(CCmdFuncPtr parm)
-{
-    char* p;
-
-    if (parm->numparms != 1) return CCMD_SHOWHELP;
-
-    int nLevel = strtol(parm->parms[0], &p, 10);
-    if (p[0]) return CCMD_SHOWHELP;
-
-    if (nLevel < 0) return CCMD_SHOWHELP;
-
-    int nMaxLevels;
-
-    if (!ISDEMOVER) {
-        nMaxLevels = 32;
-    }
-    else {
-        nMaxLevels = 4;
-    }
-
-    if (nLevel > nMaxLevels)
-    {
-        Printf("changelevel: invalid level number\n");
-        return CCMD_SHOWHELP;
-    }
-
-    GameAction = nLevel;
-
-    return CCMD_OK;
-}
-
 static int osdcmd_warptocoords(CCmdFuncPtr parm)
 {
     if (parm->numparms < 3 || parm->numparms > 5)
@@ -117,12 +60,6 @@ static int osdcmd_warptocoords(CCmdFuncPtr parm)
     return CCMD_OK;
 }
 
-static int osdcmd_exitmap(CCmdFuncPtr parm)
-{
-    EndLevel = true;
-    return CCMD_OK;
-}
-
 static int osdcmd_doors(CCmdFuncPtr parm)
 {
     for (int i = 0; i < kMaxChannels; i++)
@@ -195,9 +132,6 @@ static int osdcmd_noop(CCmdFuncPtr parm)
 int32_t registerosdcommands(void)
 {
     //if (VOLUMEONE)
-    C_RegisterFunction("levelwarp","levelwarp <level>: warps to the given level", osdcmd_changelevel);
-    C_RegisterFunction("map","map <mapname>: loads the given map", osdcmd_map);
-    C_RegisterFunction("exitmap", "exits current map", osdcmd_exitmap);
     C_RegisterFunction("doors", "opens/closes doors", osdcmd_doors);
     C_RegisterFunction("spawn","spawn <creaturetype>: spawns a creature",osdcmd_spawn);
     C_RegisterFunction("warptocoords","warptocoords [x] [y] [z] [ang] (optional) [horiz] (optional): warps the player to the specified coordinates",osdcmd_warptocoords);
diff --git a/source/exhumed/src/player.cpp b/source/exhumed/src/player.cpp
index 6d16b3119..6bfe47b13 100644
--- a/source/exhumed/src/player.cpp
+++ b/source/exhumed/src/player.cpp
@@ -1065,7 +1065,7 @@ void FuncPlayer(int a, int nDamage, int nRun)
                 }
                 else
                 {
-                    EndLevel = true;
+                    EndLevel = 13;
                 }
 
                 return;
@@ -2462,7 +2462,7 @@ do_default_b:
                             {
                                 if (!bInDemo) 
                                 {
-                                    EndLevel = true;
+                                    EndLevel = 13;
                                 }
 
                                 DestroyItemAnim(nValB);
diff --git a/source/exhumed/src/ramses.cpp b/source/exhumed/src/ramses.cpp
index ba973ce1f..b7e467e01 100644
--- a/source/exhumed/src/ramses.cpp
+++ b/source/exhumed/src/ramses.cpp
@@ -490,7 +490,7 @@ int DoSpiritHead()
             {
                 if (!CDplaying())
                 {
-                    EndLevel = 2;
+                    EndLevel = 1;
                 }
             }
         }
diff --git a/source/exhumed/src/save.cpp b/source/exhumed/src/save.cpp
index 436b9623c..8ba948533 100644
--- a/source/exhumed/src/save.cpp
+++ b/source/exhumed/src/save.cpp
@@ -60,7 +60,6 @@ bool GameInterface::LoadGame(FSaveGameNode* sv)
     pSky->yscale = 65536;
     parallaxtype = 2;
     g_visibility = 2048;
-    GameAction = 1000;
 
     if (currentLevel->levelNumber > 15)
     {
diff --git a/source/exhumed/src/view.cpp b/source/exhumed/src/view.cpp
index 01c21b877..1d19af1b2 100644
--- a/source/exhumed/src/view.cpp
+++ b/source/exhumed/src/view.cpp
@@ -436,7 +436,7 @@ void DrawView(double smoothRatio, bool sceneonly)
                     if ((bSubTitles && !subtitleOverlay.AdvanceCinemaText(myclock)) || inputState.CheckAllInput())
                     {
                         inputState.ClearAllInput();
-                        EndLevel = 2;
+                        EndLevel = 1;
 
                         if (CDplaying()) {
                             fadecdaudio();