diff --git a/src/Sourcefile b/src/Sourcefile
index fa82f86da88219d0604f6160a3abd2b784eec6f7..de90bb60910286242ae4b62b41af5d9ad57706ad 100644
--- a/src/Sourcefile
+++ b/src/Sourcefile
@@ -18,6 +18,7 @@ am_map.c
 command.c
 console.c
 hu_stuff.c
+i_time.c
 y_inter.c
 st_stuff.c
 m_aatree.c
diff --git a/src/android/i_system.c b/src/android/i_system.c
index 7256bac345c11b59e5cb39b25e30f29013f9b241..e6e840ac06f772a8765b0918c26abaa0ee08fd60 100644
--- a/src/android/i_system.c
+++ b/src/android/i_system.c
@@ -82,18 +82,6 @@ INT64 current_time_in_ps() {
   return (t.tv_sec * (INT64)1000000) + t.tv_usec;
 }
 
-tic_t I_GetTime(void)
-{
-  INT64 since_start = current_time_in_ps() - start_time;
-  return (since_start*TICRATE)/1000000;
-}
-
-fixed_t I_GetTimeFrac(void)
-{
-  //stub
-  return 0;
-}
-
 void I_Sleep(void){}
 
 void I_GetEvent(void){}
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 74f3dd959951a20c961ca2e3b50dc5fa9eba217b..ac0ccc05484436dd86d0d5ecbeb4abf2e23ca176 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -15,6 +15,7 @@
 #include <unistd.h> //for unlink
 #endif
 
+#include "i_time.h"
 #include "i_net.h"
 #include "i_system.h"
 #include "i_video.h"
diff --git a/src/d_main.c b/src/d_main.c
index ec5b354cde54f3dbb5a4efe1e41bb7590217ef1f..b2f10988a3d09d5aa6620a9d37d35a157bb1175b 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -40,6 +40,7 @@
 #include "hu_stuff.h"
 #include "i_sound.h"
 #include "i_system.h"
+#include "i_time.h"
 #include "i_threads.h"
 #include "i_video.h"
 #include "m_argv.h"
@@ -695,10 +696,9 @@ tic_t rendergametic;
 
 void D_SRB2Loop(void)
 {
-	tic_t oldentertics = 0, entertic = 0, realtics = 0, rendertimeout = INFTICS;
+	tic_t realtics = 0, rendertimeout = INFTICS;
 	static lumpnum_t gstartuplumpnum;
 
-	boolean ticked = false;
 	boolean interp = false;
 	boolean doDisplay = false;
 	boolean screenUpdate = false;
@@ -715,7 +715,7 @@ void D_SRB2Loop(void)
 	I_DoStartupMouse();
 #endif
 
-	oldentertics = I_GetTime();
+	I_UpdateTime(cv_timescale.value);
 
 	// end of loading screen: CONS_Printf() will no more call FinishUpdate()
 	con_refresh = false;
@@ -757,17 +757,18 @@ void D_SRB2Loop(void)
 	{
 		frameEnd = I_GetFrameTime();
 
+		I_UpdateTime(cv_timescale.value);
+
+		// Can't guarantee that I_UpdateTime won't be called inside TryRunTics
+		// so capture the realtics for later use
+		realtics = g_time.realtics;
+
 		if (lastwipetic)
 		{
-			oldentertics = lastwipetic;
+			// oldentertics = lastwipetic;
 			lastwipetic = 0;
 		}
 
-		// get real tics
-		entertic = I_GetTime();
-		realtics = entertic - oldentertics;
-		oldentertics = entertic;
-
 		if (demoplayback && gamestate == GS_LEVEL)
 		{
 			// Nicer place to put this.
@@ -782,7 +783,6 @@ void D_SRB2Loop(void)
 
 		interp = R_UsingFrameInterpolation() && !dedicated;
 		doDisplay = screenUpdate = false;
-		ticked = false;
 
 #ifdef HW3SOUND
 		HW3S_BeginFrameUpdate();
@@ -798,16 +798,16 @@ void D_SRB2Loop(void)
 				realtics = 1;
 
 			// process tics (but maybe not if realtic == 0)
-			ticked = TryRunTics(realtics);
+			TryRunTics(realtics);
 
 			if (lastdraw || singletics || gametic > rendergametic)
 			{
 				rendergametic = gametic;
-				rendertimeout = entertic+TICRATE/17;
+				rendertimeout = g_time.time + TICRATE/17;
 
 				doDisplay = true;
 			}
-			else if (rendertimeout < entertic) // in case the server hang or netsplit
+			else if (rendertimeout < g_time.time) // in case the server hang or netsplit
 			{
 				// Lagless camera! Yay!
 				if (gamestate == GS_LEVEL && netgame)
@@ -836,41 +836,21 @@ void D_SRB2Loop(void)
 
 		if (interp)
 		{
-			static float tictime = 0.0f;
-			static float prevtime = 0.0f;
-			float entertime = I_GetTimeFrac();
-
-			fixed_t entertimefrac = FRACUNIT;
-
-			if (ticked)
-			{
-				tictime = entertime;
-			}
-
 			// I looked at the possibility of putting in a float drawer for
 			// perfstats and it's very complicated, so we'll just do this instead...
-			ps_interp_frac.value.p = (precise_t)((entertime - tictime) * 1000.0f);
-			ps_interp_lag.value.p = (precise_t)((entertime - prevtime) * 1000.0f);
+			ps_interp_frac.value.p = (precise_t)((FIXED_TO_FLOAT(g_time.timefrac)) * 1000.0f);
+			ps_interp_lag.value.p = (precise_t)((FIXED_TO_FLOAT(g_time.deltaseconds)) * 1000.0f);
+
+			renderdeltatics = g_time.deltatics;
 
 			if (!(paused || P_AutoPause()))
 			{
-				if (entertime - prevtime >= 1.0f)
-				{
-					// Lagged for more frames than a gametic...
-					// No need for interpolation.
-					entertimefrac = FRACUNIT;
-				}
-				else
-				{
-					entertimefrac = min(FRACUNIT, FLOAT_TO_FIXED(entertime - tictime));
-				}
-
-				// renderdeltatics is a bit awkard to evaluate, since the system time interface is whole tic-based
-				renderdeltatics = FloatToFixed(entertime - prevtime);
-				rendertimefrac = entertimefrac;
+				rendertimefrac = g_time.timefrac;
+			}
+			else
+			{
+				rendertimefrac = FRACUNIT;
 			}
-
-			prevtime = entertime;
 		}
 		else
 		{
@@ -1398,8 +1378,8 @@ void D_SRB2Main(void)
 	//---------------------------------------------------- READY TIME
 	// we need to check for dedicated before initialization of some subsystems
 
-	CONS_Printf("I_StartupTimer()...\n");
-	I_StartupTimer();
+	CONS_Printf("I_InitializeTime()...\n");
+	I_InitializeTime();
 
 	// Make backups of some SOCcable tables.
 	P_BackupTables();
diff --git a/src/d_net.c b/src/d_net.c
index 5e5c10889c2a3f2a0383f005107d0ce277e7082c..c2a86481f5f0aba2f07c63838bd805bba8731b97 100644
--- a/src/d_net.c
+++ b/src/d_net.c
@@ -18,6 +18,7 @@
 
 #include "doomdef.h"
 #include "g_game.h"
+#include "i_time.h"
 #include "i_net.h"
 #include "i_system.h"
 #include "m_argv.h"
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 9b14e0913ab605235c634cd80ee405d24149294e..024f4fedec903c807436b5dc40950f624a7d0eca 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -16,6 +16,7 @@
 
 #include "console.h"
 #include "command.h"
+#include "i_time.h"
 #include "i_system.h"
 #include "g_game.h"
 #include "hu_stuff.h"
diff --git a/src/d_netfil.c b/src/d_netfil.c
index 37fb7265f8abe714d90dab7c0882f55d73bc1cb0..edbef30bbf92ba6156da44f3d30fb32d8ab0cccb 100644
--- a/src/d_netfil.c
+++ b/src/d_netfil.c
@@ -35,6 +35,7 @@
 #include "doomstat.h"
 #include "d_main.h"
 #include "g_game.h"
+#include "i_time.h"
 #include "i_net.h"
 #include "i_system.h"
 #include "m_argv.h"
diff --git a/src/dummy/i_system.c b/src/dummy/i_system.c
index f3d0bc5e8bf15ea68695eaddb38af3c10e7063b2..d11e8e576f57658c2a8eefd8e2838c7b923ff938 100644
--- a/src/dummy/i_system.c
+++ b/src/dummy/i_system.c
@@ -11,16 +11,6 @@ UINT32 I_GetFreeMem(UINT32 *total)
 	return 0;
 }
 
-tic_t I_GetTime(void)
-{
-	return 0;
-}
-
-fixed_t I_GetTimeFrac(void)
-{
-	return 0;
-}
-
 void I_Sleep(void){}
 
 void I_GetEvent(void){}
diff --git a/src/f_finale.c b/src/f_finale.c
index 4ddcaff5f18df26a6f399f53c952bc7f3f97477f..a26600101a850961acb94a303f0097a25067fe25 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -20,6 +20,7 @@
 #include "hu_stuff.h"
 #include "r_local.h"
 #include "s_sound.h"
+#include "i_time.h"
 #include "i_video.h"
 #include "v_video.h"
 #include "w_wad.h"
diff --git a/src/f_wipe.c b/src/f_wipe.c
index 43b7180b754408faf39387ef259d54aae590bf9f..612d2ab830328622c529b4186d4f3138bfeb4a46 100644
--- a/src/f_wipe.c
+++ b/src/f_wipe.c
@@ -24,6 +24,7 @@
 #include "w_wad.h"
 #include "z_zone.h"
 
+#include "i_time.h"
 #include "i_system.h"
 #include "i_threads.h"
 #include "m_menu.h"
diff --git a/src/g_demo.c b/src/g_demo.c
index 7a72369455039121615f48e1c6c611ca7c15a35f..9a0d5a2934a406a7bc606ad23670b87c669658d6 100644
--- a/src/g_demo.c
+++ b/src/g_demo.c
@@ -17,6 +17,7 @@
 #include "d_player.h"
 #include "d_clisrv.h"
 #include "p_setup.h"
+#include "i_time.h"
 #include "i_system.h"
 #include "m_random.h"
 #include "p_local.h"
diff --git a/src/g_game.c b/src/g_game.c
index 8eb731b1ff3675c523b46cc203fc2d55086ada27..dfc5fadd5e38844e7db7324dbd7d28899d1c2797 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -19,6 +19,7 @@
 #include "f_finale.h"
 #include "p_setup.h"
 #include "p_saveg.h"
+#include "i_time.h"
 #include "i_system.h"
 #include "am_map.h"
 #include "m_random.h"
diff --git a/src/i_system.h b/src/i_system.h
index f607ec79cc670b9d5558ce9856e8d49f8ee873ff..bf4627d5438fbe1a422fdd6732949c90260d5569 100644
--- a/src/i_system.h
+++ b/src/i_system.h
@@ -42,14 +42,6 @@ extern UINT8 keyboard_started;
 */
 UINT32 I_GetFreeMem(UINT32 *total);
 
-/**	\brief  Called by D_SRB2Loop, returns current time in game tics.
-*/
-tic_t I_GetTime(void);
-
-/** \brief  Get the current time in game tics, including fractions.
-*/
-float I_GetTimeFrac(void);
-
 /**	\brief	Returns precise time value for performance measurement.
   */
 precise_t I_GetPreciseTime(void);
@@ -58,6 +50,10 @@ precise_t I_GetPreciseTime(void);
   */
 int I_PreciseToMicros(precise_t d);
 
+/** \brief  Calculates the elapsed microseconds between two precise_t.
+  */
+double I_PreciseElapsedSeconds(precise_t before, precise_t after);
+
 /** \brief  Get the current time in rendering tics, including fractions.
 */
 double I_GetFrameTime(void);
diff --git a/src/i_time.c b/src/i_time.c
new file mode 100644
index 0000000000000000000000000000000000000000..93f9b3410d3c90489dc1cdee0f4b8373bac244ff
--- /dev/null
+++ b/src/i_time.c
@@ -0,0 +1,86 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1993-1996 by id Software, Inc.
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  i_time.c
+/// \brief Timing for the system layer.
+
+#include "i_time.h"
+
+#include "command.h"
+#include "doomtype.h"
+#include "m_fixed.h"
+#include "i_system.h"
+
+timestate_t g_time;
+
+static CV_PossibleValue_t timescale_cons_t[] = {{FRACUNIT/20, "MIN"}, {20*FRACUNIT, "MAX"}, {0, NULL}};
+consvar_t cv_timescale = CVAR_INIT ("timescale", "1.0", CV_NETVAR|CV_CHEAT|CV_FLOAT, timescale_cons_t, NULL);
+
+static precise_t enterprecise, oldenterprecise;
+static fixed_t entertic, oldentertics;
+static double tictimer;
+
+tic_t I_GetTime(void)
+{
+	return g_time.time;
+}
+
+void I_InitializeTime(void)
+{
+	g_time.time = 0;
+	g_time.timefrac = 0;
+	g_time.realtics = 0;
+	g_time.deltaseconds = 0;
+	g_time.ticrate = FLOAT_TO_FIXED(TICRATE);
+
+	enterprecise = 0;
+	oldenterprecise = 0;
+	tictimer = 0.0;
+
+	CV_RegisterVar(&cv_timescale);
+
+	// I_StartupTimer is preserved for potential subsystems that need to setup
+	// timing information for I_GetPreciseTime and sleeping
+	I_StartupTimer();
+}
+
+void I_UpdateTime(fixed_t timescale)
+{
+	double ticratescaled;
+	double elapsedseconds;
+	tic_t realtics;
+
+	// get real tics
+	ticratescaled = (double)TICRATE * FIXED_TO_FLOAT(timescale);
+
+	enterprecise = I_GetPreciseTime();
+	elapsedseconds = I_PreciseElapsedSeconds(oldenterprecise, enterprecise);
+	tictimer += elapsedseconds;
+	while (tictimer > 1.0/ticratescaled)
+	{
+		entertic += 1;
+		tictimer -= 1.0/ticratescaled;
+	}
+	realtics = entertic - oldentertics;
+	oldentertics = entertic;
+	oldenterprecise = enterprecise;
+
+	// Update global time state
+	g_time.time += realtics;
+	{
+		double fractional, integral;
+		fractional = modf(tictimer * ticratescaled, &integral);
+		g_time.timefrac = FLOAT_TO_FIXED(fractional);
+	}
+	g_time.realtics = realtics;
+	g_time.deltatics = FLOAT_TO_FIXED(elapsedseconds * ticratescaled);
+	g_time.deltaseconds = FLOAT_TO_FIXED(elapsedseconds);
+	g_time.ticrate = FLOAT_TO_FIXED(ticratescaled);
+}
diff --git a/src/i_time.h b/src/i_time.h
new file mode 100644
index 0000000000000000000000000000000000000000..0771747566ae73f135eefb22b576b052fde3e3fd
--- /dev/null
+++ b/src/i_time.h
@@ -0,0 +1,51 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1993-1996 by id Software, Inc.
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1999-2022 by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  i_time.h
+/// \brief Timing for the system layer.
+
+#ifndef __I_TIME_H__
+#define __I_TIME_H__
+
+#include "command.h"
+#include "doomtype.h"
+#include "m_fixed.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct timestate_s {
+	tic_t time;
+	fixed_t timefrac;
+	fixed_t realtics;
+	fixed_t deltatics;
+	fixed_t deltaseconds;
+	fixed_t ticrate;
+} timestate_t;
+
+extern timestate_t g_time;
+extern consvar_t cv_timescale;
+
+/**	\brief  Called by D_SRB2Loop, returns current time in game tics.
+*/
+tic_t I_GetTime(void);
+
+/**	\brief  Initializes timing system.
+*/
+void I_InitializeTime(void);
+
+void I_UpdateTime(fixed_t timescale);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // __I_TIME_H__
diff --git a/src/m_menu.c b/src/m_menu.c
index d32782ed1a54233504762eafa70c990d1b6d5b46..0c0f903866687ade6dfc9662430fed39c63c32eb 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -32,6 +32,7 @@
 // Data.
 #include "sounds.h"
 #include "s_sound.h"
+#include "i_time.h"
 #include "i_system.h"
 #include "i_threads.h"
 
diff --git a/src/m_misc.c b/src/m_misc.c
index d7d6d6bbb6e9d4931c545b985e386d303af54941..a400865f313459e09bc45c0d7b2cec1949c3e290 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -36,6 +36,7 @@
 #include "v_video.h"
 #include "z_zone.h"
 #include "g_input.h"
+#include "i_time.h"
 #include "i_video.h"
 #include "d_main.h"
 #include "m_argv.h"
diff --git a/src/p_setup.c b/src/p_setup.c
index 0e7432bbf81dc03049dfc3a792905f4917efd4ea..20c6952d9236506077931e5619744f4a849f9458 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -21,6 +21,7 @@
 #include "p_spec.h"
 #include "p_saveg.h"
 
+#include "i_time.h"
 #include "i_sound.h" // for I_PlayCD()..
 #include "i_video.h" // for I_FinishUpdate()..
 #include "r_sky.h"
diff --git a/src/screen.c b/src/screen.c
index 6ad36273b7488ef4ebe7d8f88a20b4e1a438a278..62b1fe05cd0c25c8a8ae55aff4be508ecc40a60f 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -15,6 +15,7 @@
 #include "screen.h"
 #include "console.h"
 #include "am_map.h"
+#include "i_time.h"
 #include "i_system.h"
 #include "i_video.h"
 #include "r_local.h"
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index afd13910cb013702cfc61980ed593480a3f437f1..de7369236322a07997c331a3e06ab8e6e71dd126 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -170,6 +170,7 @@ static char returnWadPath[256];
 
 #include "../doomdef.h"
 #include "../m_misc.h"
+#include "../i_time.h"
 #include "../i_video.h"
 #include "../i_sound.h"
 #include "../i_system.h"
@@ -2143,44 +2144,6 @@ ticcmd_t *I_BaseTiccmd2(void)
 
 static Uint64 timer_frequency;
 
-static double tic_frequency;
-static Uint64 tic_epoch;
-static double elapsed_tics;
-
-static void UpdateTicFreq(void)
-{
-	tic_frequency = (timer_frequency / (double)NEWTICRATE) / FixedToFloat(cv_timescale.value);
-}
-
-static CV_PossibleValue_t timescale_cons_t[] = {{FRACUNIT/20, "MIN"}, {20*FRACUNIT, "MAX"}, {0, NULL}};
-consvar_t cv_timescale = CVAR_INIT ("timescale", "1.0", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_CALL, timescale_cons_t, UpdateTicFreq);
-
-static void UpdateElapsedTics(void)
-{
-	const Uint64 now = SDL_GetPerformanceCounter();
-
-	elapsed_tics += (now - tic_epoch) / tic_frequency;
-	tic_epoch = now; // moving epoch
-}
-
-tic_t I_GetTime(void)
-{
-	float f = 0.0f;
-
-	UpdateElapsedTics();
-
-	// This needs kept in a separate variable before converting
-	// to tic_t, due to stupid -Wbad-function-cast error.
-	f = floor(elapsed_tics);
-	return (tic_t)f;
-}
-
-float I_GetTimeFrac(void)
-{
-	UpdateElapsedTics();
-	return elapsed_tics;
-}
-
 //
 // I_GetPreciseTime
 // returns time in precise_t
@@ -2201,6 +2164,11 @@ int I_PreciseToMicros(precise_t d)
 	return (int)(UINT64)(d / (timer_frequency / 1000000.0));
 }
 
+double I_PreciseElapsedSeconds(precise_t before, precise_t after)
+{
+	return (after - before) / (double)timer_frequency;
+}
+
 //
 // I_GetFrameTime
 // returns time in 1/fpscap second tics
@@ -2259,15 +2227,9 @@ double I_GetFrameTime(void)
 //
 void I_StartupTimer(void)
 {
-	CV_RegisterVar(&cv_timescale);
-
 	timer_frequency = SDL_GetPerformanceFrequency();
-	tic_epoch       = SDL_GetPerformanceCounter();
 
-	tic_frequency   = timer_frequency / (double)NEWTICRATE;
-	elapsed_tics    = 0.0;
-
-	I_InitFrameTime(tic_epoch, R_GetFramerateCap());
+	I_InitFrameTime(0, R_GetFramerateCap());
 	elapsed_frames  = 0.0;
 }
 
@@ -2279,6 +2241,10 @@ void I_Sleep(void)
 {
 	if (cv_sleep.value > 0)
 		SDL_Delay(cv_sleep.value);
+
+	// I_Sleep is still called in a number of places
+	// we need to update the internal time state to make this work
+	I_UpdateTime(cv_timescale.value);
 }
 
 //
diff --git a/src/w_wad.c b/src/w_wad.c
index 0a8f630a804b2ea2fda9e83b902adb3af66295d1..b2461464765d20bdbbf580c919fd506347fa6711 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -59,6 +59,7 @@
 #include "r_textures.h"
 #include "r_patch.h"
 #include "r_picformats.h"
+#include "i_time.h"
 #include "i_system.h"
 #include "i_video.h" // rendermode
 #include "md5.h"
diff --git a/src/win32/win_sys.c b/src/win32/win_sys.c
index 393b913dadf688f3c38372c6a40d8e768615fdbe..7e6f82a29b5ea4f3b45e75258324db386018dc62 100644
--- a/src/win32/win_sys.c
+++ b/src/win32/win_sys.c
@@ -35,6 +35,7 @@
 #include <mmsystem.h>
 
 #include "../m_misc.h"
+#include "../i_time.h"
 #include "../i_video.h"
 #include "../i_sound.h"
 #include "../i_system.h"
@@ -262,15 +263,12 @@ tic_t I_GetTime(void)
 	return newtics;
 }
 
-fixed_t I_GetTimeFrac(void)
-{
-	return 0;
-}
-
 void I_Sleep(void)
 {
 	if (cv_sleep.value != -1)
 		Sleep(cv_sleep.value);
+
+	I_UpdateTime(cv_timescale.value);
 }
 
 // should move to i_video