Index: framework/Session.cpp
===================================================================
--- framework/Session.cpp	(revision 9980)
+++ framework/Session.cpp	(working copy)
@@ -39,6 +39,11 @@
 	"Never do more than this number of game tics per one frame. "
 	"When frames take too much time, allow game time to run slower than astronomical time.",
 1, 1000);
+idCVar com_maxPhysicsTimestep("com_maxPhysicsTimestep", "17", CVAR_SYSTEM | CVAR_INTEGER,
+	"Timestep in physics modelling must not exceed this number of milliseconds. "
+	"If game tic takes longer, then several physics timesteps are modelled in one tic.\n"
+	"Ideally, this cvar should be ceil(com_maxTicTimestep / k) for some k.",
+1, 1000);
 idCVar	idSessionLocal::com_maxFPS( "com_maxFPS", "166", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_INTEGER, "define the maximum FPS cap", 2, 1000 );
 idCVar	idSessionLocal::com_showDemo("com_showDemo", "0", CVAR_SYSTEM | CVAR_BOOL, "");
 idCVar	idSessionLocal::com_skipGameDraw( "com_skipGameDraw", "0", CVAR_SYSTEM | CVAR_BOOL, "" );
Index: game/Entity.cpp
===================================================================
--- game/Entity.cpp	(revision 9980)
+++ game/Entity.cpp	(working copy)
@@ -5400,8 +5400,31 @@
 		if ( part->physics )
 		{
 			// run physics
-			moved = part->physics->Evaluate( endTime - startTime, endTime );
+			int deltaTime = endTime - startTime;
+			int numTics = 1;
+			if ( deltaTime > com_maxPhysicsTimestep.GetInteger() )
+				numTics = ( (deltaTime - 1) / com_maxPhysicsTimestep.GetInteger() ) + 1;
+			// for AI, force one physics step: that's compatible with interleaved thinking
+			if ( IsType(idAI::Type) && !((idAI*)this)->IsActiveAF() )
+				numTics = 1;
 
+			moved = false;
+			for (int s = 0; s < numTics; s++) {
+				int ticBeg = startTime + deltaTime * (s + 0) / numTics;
+				int ticEnd = startTime + deltaTime * (s + 1) / numTics;
+
+				// hack: update grabber velocity/force every physics subtic
+				// also update player's push force (it sets velocity)
+				if ( gameLocal.m_Grabber->GetSelected() == this )
+					gameLocal.m_Grabber->ReEvaluate( ticEnd );
+				if ( m_pushedBy.IsValid() && m_pushedBy.GetEntity()->IsType(idPlayer::Type) )
+					((idPlayer*)m_pushedBy.GetEntity())->GetPlayerPhysics()->ReEvaluatePushForce( ticEnd );
+				// TODO: do this for all forces acting on the physics object?
+
+				if ( part->physics->Evaluate( ticEnd - ticBeg, ticEnd ) )
+					moved = true;
+			}
+
 			// check if the object is blocked
 			blockingEntity = part->physics->GetBlockingEntity();
 			if ( blockingEntity ) {
Index: game/Force_Grab.cpp
===================================================================
--- game/Force_Grab.cpp	(revision 9980)
+++ game/Force_Grab.cpp	(working copy)
@@ -628,6 +628,20 @@
 
 /*
 ================
+CForce_Grab::ReEvaluate
+================
+*/
+void CForce_Grab::ReEvaluate( int time ) {
+	// Evaluate only sets velocities and applies forces
+	// so we can call if as many times as we want on every game tic
+	// calling it after every physics substep allows to make grabbing stable
+	// (even on low FPS = several physics subtics per game tic)
+	Evaluate( time );
+}
+
+
+/*
+================
 CForce_Grab::RemovePhysics
 ================
 */
Index: game/Force_Grab.h
===================================================================
--- game/Force_Grab.h	(revision 9980)
+++ game/Force_Grab.h	(working copy)
@@ -74,6 +74,7 @@
 
 	public: // common force interface
 		virtual void		Evaluate( int time ) override;
+		virtual void		ReEvaluate( int time ) override;
 		virtual void		RemovePhysics( const idPhysics *phys ) override;
 
 	protected:
Index: game/gamesys/SysCvar.h
===================================================================
--- game/gamesys/SysCvar.h	(revision 9980)
+++ game/gamesys/SysCvar.h	(working copy)
@@ -268,6 +268,7 @@
 extern idCVar cv_weapon_next_on_empty;
 
 // physics
+extern idCVar com_maxPhysicsTimestep;
 extern idCVar cv_collision_damage_scale_vert;
 extern idCVar cv_collision_damage_scale_horiz;
 extern idCVar cv_dragged_item_highlight;
Index: game/Grabber.cpp
===================================================================
--- game/Grabber.cpp	(revision 9980)
+++ game/Grabber.cpp	(working copy)
@@ -617,6 +617,11 @@
 	return;
 }
 
+void CGrabber::ReEvaluate( int time ) 
+{
+	m_drag.ReEvaluate( time );
+}
+
 void CGrabber::StartDrag( idPlayer *player, idEntity *newEnt, int bodyID, bool preservePosition ) 
 {
 	idVec3 viewPoint, origin, COM, COMWorld, delta2;
Index: game/Grabber.h
===================================================================
--- game/Grabber.h	(revision 9980)
+++ game/Grabber.h	(working copy)
@@ -52,6 +52,8 @@
 		**/
 		void					Update( idPlayer *player, bool hold = false, bool preservePosition = false );
 
+		void					ReEvaluate( int time );
+
 		void					Save( idSaveGame *savefile ) const;
 		void					Restore( idRestoreGame *savefile );
 
Index: game/physics/Force.cpp
===================================================================
--- game/physics/Force.cpp	(revision 9980)
+++ game/physics/Force.cpp	(working copy)
@@ -75,6 +75,14 @@
 
 /*
 ================
+idForce::ReEvaluate
+================
+*/
+void idForce::ReEvaluate( int time ) {
+}
+
+/*
+================
 idForce::RemovePhysics
 ================
 */
Index: game/physics/Force.h
===================================================================
--- game/physics/Force.h	(revision 9980)
+++ game/physics/Force.h	(working copy)
@@ -42,6 +42,9 @@
 public: // common force interface
 						// evaluate the force up to the given time
 	virtual void		Evaluate( int time );
+						// stgatilov: evaluate again, probably refining velocity/force
+						// this is sed for Force_Grab on low FPS, so that it has full control on very physics subtic
+	virtual void		ReEvaluate( int time );
 						// removes any pointers to the physics object
 	virtual void		RemovePhysics( const idPhysics *phys );
 
Index: game/physics/Force_Push.cpp
===================================================================
--- game/physics/Force_Push.cpp	(revision 9980)
+++ game/physics/Force_Push.cpp	(working copy)
@@ -29,7 +29,8 @@
 	id(0),
 	impactVelocity(vec3_zero),
 	startPushTime(-1),
-	owner(NULL)
+	owner(NULL),
+	pushingByVelocity(false)
 {
 	lastPushEnt = NULL;
 	memset(&contactInfo, 0, sizeof(contactInfo));
@@ -99,6 +100,8 @@
 {
 	if (owner == NULL) return;
 
+	pushingByVelocity = false;
+
 	if (pushEnt == NULL) 
 	{
 		// nothing to do, but update the owning actor's push state
@@ -218,6 +221,8 @@
 
 			// Apply the mass scale and the acceleration scale to the capped velocity
 			physics->SetLinearVelocity(pushVelocity * velocity * accelScale * massScale * entityScale);
+			// stgatilov: in case we model several physics subtics per game tic, reset this velocity constantly
+			pushingByVelocity = true;
 
 			DM_LOG(LC_MOVEMENT, LT_INFO)LOGSTRING("Pushing obstacle %s\r", pushEnt->name.c_str());
 
@@ -242,6 +247,15 @@
 	pushEnt = NULL;
 }
 
+void CForcePush::ReEvaluate( int time )
+{
+	if (pushingByVelocity) {
+		// we need to reset object's velocity every physics subtic
+		pushEnt = lastPushEnt.GetEntity();
+		Evaluate(time);
+	}
+}
+
 void CForcePush::SetOwnerIsPushing(bool isPushing)
 {
 	// Update the owning actor's push state if it has changed
Index: game/physics/Force_Push.h
===================================================================
--- game/physics/Force_Push.h	(revision 9980)
+++ game/physics/Force_Push.h	(working copy)
@@ -44,6 +44,7 @@
 
 public: // common force interface
 	virtual void		Evaluate( int time ) override;
+	virtual void		ReEvaluate( int time ) override;
 
 private:
 	void				SetOwnerIsPushing(bool isPushing);
@@ -56,6 +57,7 @@
 	trace_t				contactInfo;	// the contact info of the object we're pushing
 	idVec3				impactVelocity;	// the velocity the owner had at impact time
 	int					startPushTime;	// the time we started to push the physics object
+	bool				pushingByVelocity;	// true if we need to constantly set velocity (false if we just apply impulses)
 
 	idEntity*			owner;			// the owning entity
 };
Index: game/physics/Physics_AF.cpp
===================================================================
--- game/physics/Physics_AF.cpp	(revision 9980)
+++ game/physics/Physics_AF.cpp	(working copy)
@@ -6686,8 +6686,9 @@
 		// we'll take them into account on the next physics subtic
 		body->current->externalForce.Zero();
 		body->next->externalForce.Zero();
-		// drop list of forces
-		body->forceApplications.Clear();
+		// drop list of forces if this is the last Evaluate on the current game tic
+		if ( endTimeMSec == gameLocal.time )
+			body->forceApplications.Clear();
 	}
 
 	// apply contact force to other entities
Index: game/physics/Physics_Player.cpp
===================================================================
--- game/physics/Physics_Player.cpp	(revision 9980)
+++ game/physics/Physics_Player.cpp	(working copy)
@@ -3583,6 +3583,16 @@
 
 /*
 ================
+idPhysics_Player::ReEvaluatePushForce
+================
+*/
+void idPhysics_Player::ReEvaluatePushForce( int time ) {
+	// stgatilov: update push force, letting it set desired velocity again
+	m_PushForce->ReEvaluate( time );
+}
+
+/*
+================
 idPhysics_Player::UpdateTime
 ================
 */
Index: game/physics/Physics_Player.h
===================================================================
--- game/physics/Physics_Player.h	(revision 9980)
+++ game/physics/Physics_Player.h	(working copy)
@@ -243,6 +243,8 @@
 	bool					CheckPushEntity(idEntity *entity); // grayman #4603
 	void					ClearPushEntity(); // grayman #4603
 
+	void					ReEvaluatePushForce( int time );
+
 private:
 	// player physics state
 	playerPState_t			current;
Index: game/physics/Physics_RigidBody.cpp
===================================================================
--- game/physics/Physics_RigidBody.cpp	(revision 9980)
+++ game/physics/Physics_RigidBody.cpp	(working copy)
@@ -1672,9 +1672,11 @@
 		current.i.position + centerOfMass * current.i.orientation,
 		&externalForce, &externalTorque, &externalForcePoint
 	);
-	// end of game tic: clear force list
-	// forces will not show up on next game tic unless reapplied via AddForce
-	current.forceApplications.Clear();
+	if ( endTimeMSec == gameLocal.time ) {
+		// end of game tic: clear force list
+		// forces will not show up on next game tic unless reapplied via AddForce
+		current.forceApplications.Clear();
+	}
 
 	if ( hasMaster ) {
 		oldOrigin = current.i.position;
