This is an automated archive made by the Lemmit Bot.

The original was posted on /r/globaloffensive by /u/carnifexCSGO on 2023-11-06 14:09:01.


Yes, I am going to be the person to say that Valve, who wrote this game, is wrong about how it works. Valve developer John McDonald recently said that most of the problems related to how people perceive subtick is wrong because of cl_showpos. In this thread I will prove that is completely incorrect, and that subtick for movement is inconsistent in practice, and doesn’t even work in theory.

There is going to be some repetition from my previous posts here, but also some new explanations and thoughts.

What is subtick for movement?

To start, we need to understand what subtick for movement is, and despite what some may think, it is not a revolutionary new tech, it is actually an extremely simple idea and implementation.

If you are one of the people who believes that subtick movement means that you will move instantly when pressing a movement key, you are simply wrong. I have seen this idea thrown around on reddit many times. Forget what you think you know about subtick and let me explain what it actually is.

Subtick for movement is basically just 64 tick. In desubticked movement (I.e csgo or cs2 with alias binds), you will need to wait 0-15.6 ms for the player to move. This is exactly the same when we are dealing with subtick movement. You simply do not ever move earlier when dealing with subtick. You will begin moving at the exact same tick you normally would do without subtick. The gamestate is never updated earlier.

What subtick does, is practice, is changing your initial velocity. It does this by first getting a timestamp of when you pressed a button during a tick, and then the following tick is divided in two. One of the halves is the part of the tick where you don’t press any button, and the second half is the part of the tick you press a button. Together the halves make up one tick. This is a simple example, and it works the same if you do many button presses in a tick. Then it subdivides the tick even further, still adding up to 1.

Technical rundown.

I will also show you how it works on a technical level. It is not that advanced, but we need to understand how the movement code is put together. Basically, all movement code is in a file called gamemovement.cpp, with overrides specific for counter-strike in a file called cs_gamemovement.cpp. This was what it was like in csgo, but by opening the server.dll file in any disassembler, you can tell the code is 95% the same for cs2. There is also a structure called CGlobalVars, which contain information about time, frametime etc.

If you wanna have a look for yourself, you can browse the movement code for csgo, which for our purposes is mostly the same. There are a few differences I will cover below.

All movement starts from CGameMovement::ProcessMovement.

This is a simplified call tree for walking:

ProcessMovement

-> PlayerMove

—> FullWalkMove

------> Friction

------> Walkmove

-------------> Accelerate

------> CheckVelocity

Now what happens when we for example, start pressing a button halfway into a tick? Well, CGameMovement::ProcessMovement gets called twice, as the tick is divided into two parts, and its called the first time with a gpGlobals→frametime of 0.0078125 and the second time also with a frametime of 0.0078125 as both halves should be the same as we pressed exactly in the middle of the tick. These two halves make up 15.6ms. Since ProcessMovement subsequently calls every movement function, all movement functions use the same frametime as ProcessMovement. So if we take a look at the accelerate function, we can see what actually happens to our velocity.

CGameMovement::Accelerate (simplified):

accelspeed = accel * gpGlobals->frametime * kAccelerationScale * player→m_surfaceFriction;

// Adjust velocity.

for (i=0 ; i<3 ; i++)

{

  mv->m\_vecVelocity\[i\] += accelspeed \* wishdir\[i\];      

}

These are the lines of code that affects our horizontal velocity. So since we aren’t getting any velocity in the first half of the tick, we can look at the second half.

Accel = 5.5 (sv_accelerate)

frametime = 0.0078125 (0.5 * (1/64))

kAccelerationScale is 250 with knife out, 240 with most guns.

m_surfaceFriction is almost always 1. It can be lower on rarer types of surfaces like ice, mud etc. We are using 1 for our example.

If we calculate this, we will get a value of 10.7421875 u/s. If we were to have a frametime of 15.6ms, we would get a full 21.48 u/s this tick.

But there is also more. The second tick is also divided in two. The first half is the remainder of the first tick. So the frametime for the first half of tick two is 0.015625 - 10.7421875 which we can conceptualize as (1/64 – subtick_fraction), and the second half is is just the subtick fraction again.

So the full two ticks for moving is just the following

Tick 1:

ProcessMovement((1/64 – subtick_fraction) (no movement, key not down)

ProcessMovement(subtick_fraction)

Tick 2:

ProcessMovement((1/64 – subtick_fraction) (remainder of first tick)

ProcessMovement(subtick_fraction)

Tick 3 and forward:

ProcessMovement(1/64)

A difference in code between CS2 and CS:GO is that friction is now delayed until the second half of the second tick, so that friction does not eat up the velocity from tick one, which is now lower and offset due to subtick.

I will also say that this system is exactly the same for jumping as well. It all stems from ProcessMovement.

This information is gotten by debugging the game, and decompiling it and looking at the actual instructions in the games game files.

What is the intention of subtick?

Clearly, valves intention is to make the keypresses more “accurate” as in, they want to make GameMovement functions listen to when a key was pressed, and move the character accordingly. This could sound good in theory, but it isn’t.

So their idea is that if you press your movement key late, you will also start accelerating later during that tick. This is kinda whats happening but there are some consequences I guess Valve never thought much about. They believe you shouldn’t look at the velocity in ticks as they are shifted to offset from a tick as they said here:

“It reports values on tick boundaries which makes them look inconsistent—because of course they do! The movement curves have been shifted to offset from a tick.”

This is kind of the problem. The game is still tick based, so even if there movement is shifted to offset from a tick, there is actually nothing going on between ticks as its still a purely tick based game.

The problems with subtick

I will start by giving a concrete example of something that is just wrong. Since the game is still tick based, but movement is offset, there will be ugly consequences because of that. I have made a calculator and also cross-referenced with results in-game, that I have gathered by hooking into the movement functions on the server (no cl_showpos used!) to make sure everything is correct. So basically, according to my tests, accelerating from 0 u/s to 250 u/s should with desubticked binds, or in csgo, always take 35 ticks. In cs2 with subtick this is not the case. The reason gets into the heart of the problem with the subtick implementation.

Acceleration from 0 to 250 u/s using different subtick fractions

As you can see, accelerating from 0 to 250 u/s often takes 36 ticks with subtick enabled, instead of 35. Why is that? Well, since the movement is offset, and the game still updates at a fixed 64 tick rate, the movement is basically processed at the wrong time, and in practice since there is nothing between ticks, you wont reach 250 u/s until 15.6ms later than you normally would. Moving from 0 to 250 u/s should always take the same amount of time, but it doesn’t. If you manage to press your movement key precisely after the tick, you will move 250 u/s in 35 ticks, if you press a little later, you will use 36 ticks to reach max velocity. This is one example of how subtick just doesn’t work properly.

The mirage arch jump

The mirage arch jump is a test you can do yourself at the T Spawn of mirage, there is an arch you can jump into, and every time you jump, you will land at a different spot.

Despite what people usually think, its not jump height that is the problem here. A lot of the time you will actually reach the correct jump height, but you will do so at different points in time. This comes back to movement being processed at the wrong time.

The problem is basically this. You press jump later in a tick, you will start accelerating later. This means at any given tick with different jump timings, your jump velocity is going to be different. The velocity you have when hitting the arch is in all pr…


Content cut off. Read original on https://old.reddit.com/r/GlobalOffensive/comments/17p2cq3/subtick_for_movement_doesnt_even_work_in_theory/