The user provides empirical evidence that the 'jittery' spray in CS2 is caused by conflicting update rates between viewpunch (updated per frame) and recoil offset (updated per tick). They request Valve to fix this issue by synchronizing these update rates to improve the core spray mechanic, which is currently 'off'.
# "Disclaimer" This post is divided into three parts: **Part 1: Misunderstandings and Misinformation on my end** – Self-explanatory. **Part 2: The "Big" Discovery** – How I found a way to prove the issue and put aside my ego. **Part 3: The Actual Test and the Reason You're Here** – Also self-explanatory. If you're only interested in what causes the jittery spray in CS2, feel free to skip directly to Part 3. # Objective of the Test To prove that the recoil offset (also referred to as recoil cooldown or recoil recovery—I'll use "recoil offset" from here on out) is updated every tick, meaning it's tickrate-dependent (64 times per second). This is done by removing the viewpunch variable. The hypothesis is that the jitter in the spray is caused by a conflicting update rate between viewpunch (updated per frame) and recoil offset (updated per tick). # Start of the Post and Greetings Hello again, Reddit. I'm the guy behind the post ["Why the Spray Feels “Off” in CS2"](https://www.reddit.com/r/GlobalOffensive/comments/1kfffvy/why_the_spray_feels_off_in_cs2/) and you can also find me on twitter: [https://x.com/eugenio8a8](https://x.com/eugenio8a8) # Introduction In my previous post, I tried to uncover (or at least propose) the reason behind the bad feeling we all experience when spraying in CS2. I showed the actual behavior of the view angles during spraying and demonstrated that the recovery phase was jittery. Because of this, spray control felt awful. However, even with data, we couldn’t pinpoint *why* this jitter was happening. The prevailing hypothesis was that viewpunch was being updated by frame while recoil offset was updated at the tickrate (64 times per second). This conflicting update rate could cause the jitter. We needed a way to isolate recoil offset from viewpunch. But how? There are no commands to access viewpunch values—or so I thought. So, let’s dive into Part 1. # Part 1: Misunderstandings and misinformation on my end Let’s get straight to it. In my ignorance, I thought viewpunch was just a post-processing effect that only affected screen shake and not the view angles shown in cl\_showpos. I also believed kickpunch and viewpunch were two different things. Some even refer to kickpunch as aimpunch, so terminology was all over the place. Well, that misunderstanding is now cleared up: * Viewpunch and kickpunch are the same thing. * Aimpunch is the effect on screen, aim, and view angles when a player gets shot. So, from now on, I’ll just refer to it as viewpunch. I used to think viewpunch didn’t affect `cl_showpos`, but I was wrong (even though I didn’t say this explicitly in my previous post). I’m glad I was wrong. A Reddit user, pointed me to the `view_punch_decay 1000` command with this comment: >"Sorry for replying so late but: view\_punch\_decay 1000, try this. It will make viewpunch decay faster than one frame, letting you evaluate only recoil and making it very obvious that this is viewpunch and recoil offset fighting each other." He said I’d feel the difference, that I’d realize something else was fighting my control—and he was right. # Part 2: The "Big" Discovery I initially thought, "I already tried that command a long time ago. Why try again?" But eventually, I gave it another shot. And I’m *so glad* I did. In that test, I discovered that `view_punch_decay` (which controls how viewpunch decays, with a default value of 18) **does** affect view angles. Setting `view_punch_decay` to a very high value makes viewpunch decay faster than one frame—essentially removing it. This isolates the recoil offset and allows testing of the hypothesis proposed by u/WhatAwasteOf7Years, who’s been calling for a fix since CS2's beta. # Part 3: The Actual Test and the Reason You're Here Now I will be more direct in this part. I thought it was important to show the process, steps, and people that made this experiment possible. Without further delay, let’s get to it: **The objective of the test:** To prove that the recoil offset or cooldown or recoil recovery—whatever you want to call it (all are the same thing in my book, and I will refer to it as "recoil offset" from here on out)—is updated in each tick, meaning it is tickrate-dependent (64 times a second), by removing the viewpunch variable. So we can prove that the cause of the jitter in the spray is the conflicting update rate between the viewpunch (which is updated by frame) and the recoil offset. **Methodology:** Tools Used: * OCR (Optical Character Recognition) script used to extract pitch, yaw, and roll values (roll excluded from analysis). * Steam’s in-built recorder to capture gameplay at 60 fps with `cl_showpos 1` * Frame extraction software to convert video files into individual frames Games tested: CS2 **Test Environment:** **CS2** **Removing the viewpunch variable to isolate recoil offset:** If you didn’t read Part 1 and 2: viewpunch affects the view angles in `cl_showpos`, and `view_punch_decay` is a command that allows you to control how quickly the viewpunch decays (default value is 18; higher values = faster decay). By setting `view_punch_decay` to 10000, viewpunch decays faster than one frame—effectively removing viewpunch from the equation, so we can evaluate only the recoil offset update rate. **Map:** aimbots **Console Commands:** * `cl_showpos 1` * `setang 0.000000 0.000000 0.000000` * `host_timescale 0.1` * `cl_draw_only_deathnotices 1` * `r_drawblankworld` * `view_punch_decay 10000` **Spray Recording Protocol:** * `fps_max 400` * `sv_infiniteammo 2` * Weapon: AK-47 * Fire rate: 600 RPM * Spray duration: \~3 seconds * Macro tool: AutoHotkey * Host\_timescale: 0.1 Since the game was running at 10% speed, the spray duration scales like this: 3 seconds / 0.1 = 30 seconds real time To ensure complete capture, the macro was set to run for 31 seconds. **Frame Timing:** Frame duration at `host_timescale 0.1`: ef = (1 / 60) * 0.1 = 0.001667 seconds per frame Meaning each frame represents 0.001667 seconds in real time. At 64 tickrate, each tick = 1 / 64 = 0.015625 seconds **Expected Repetition in CS2:** To estimate how many frames we expect to repeat during a single tick: * Each tick is 1 / 64 = **0.015625 seconds** * Each frame (recorded at 60 FPS with `host_timescale 0.1`) is **0.001667 seconds** * **Expected identical frame count per tick = 0.015625 / 0.001667 ≈ 9.3** So we expect to see **about 9 to 10 repeated magnitude values per tick**. That’s the theoretical basis used for the comparison in the streak analysis. **Testing and Observations:** OCR software had a 97% accuracy this time, and the values that missed I manually corrected by going to the individual invalid frames. So accuracy increased to 100%. First, I will show the normal graph with the default `view_punch_decay` value for those who forgot or didn’t read my previous post: [Normal magnitude View angle behavior](https://preview.redd.it/ugmep5mh7r1f1.png?width=1805&format=png&auto=webp&s=07d64c07eecc635ded7aecd4f2d658e63430a9aa) As you can see, there’s jitter—but without removing the viewpunch, we can’t confirm the hypothesis or identify the root cause. Now I will show the graph with viewpunch removed: [recoil offset behavior](https://preview.redd.it/xhynthpj7r1f1.png?width=1809&format=png&auto=webp&s=5e2b4b0ef29bfe18dcd93820407195d6e19cdfce) Now only the recoil offset behavior is present. And to test the hypothesis from the user u/WhatAwasteOf7Years, I’ll show the streak summary. This measures how many consecutive frames reported the same magnitude values. Remember: if the hypothesis is correct, we should mostly see 9–10 repeated magnitude values per tick. [streak summary](https://preview.redd.it/kxel9p1m7r1f1.png?width=1455&format=png&auto=webp&s=53dd8300cc50bd367642d263593e9ef70874027a) There’s a total of 185 combined streaks of 9–10 repeated magnitude values per tick. We recorded 2072 frames, a total of 3.457791 seconds, so: Expected ticks = 64 × 3.457791 ≈ 221.3 Coverage = (185 / 221.3) × 100 ≈ 83.7% Comparing this to the 185 streaks, about **83.7%** of the estimated ticks are covered by those streaks. But this isn’t the end—each shot can disrupt a streak or tick update cycle. We recorded 30 shots, one every 100ms or \~6.4 ticks. So we estimate about 30 ticks couldn’t form full 9/10-frame streaks. Adjusted expected ticks = 221 - 30 = 191 Adjusted coverage = (185 / 191) × 100 ≈ 96.96% Comparing this to the adjusted expected 191 ticks, about 96.96% of them are covered by the 185 streaks of 9–10 frames. This makes it very clear that the update rate of the recoil offset matches the tickrate almost perfectly when viewpunch is removed from the equation. # Final Visual Proof: Viewpunch Is Smooth — Recoil Offset Is Not To further reinforce the hypothesis, I isolated the viewpunch effect by subtracting the corrected view angles captured with `view_punch_decay 10000` (i.e., recoil offset only) from those captured with the default `view_punch_decay` (i.e., recoil offset + viewpunch). The resulting **difference represents the viewpunch component** alone. [isolated viewpunch](https://preview.redd.it/flgyqqno7r1f1.png?width=1821&format=png&auto=webp&s=ceae6ebe7b36a058a422fe83abcab079048114e8) As shown in the graph above, the magnitude of this isolated viewpunch is smooth and continuous. This stands in stark contrast to the **stair-step jitter** observed when only recoil offset is present. The viewpunch component does not cause visible instability — **it’s the recoil offset, updated once per tick, that introduces abrupt jumps**. This graph offers **clear empirical evidence**: viewpunch is frame-synced, but recoil offset is not — and that mismatch is what causes the recoil jitter. **And as a bonus**, here’s a composite graph that visualizes all three behaviors side by side: https://preview.redd.it/dbrhxgaq7r1f1.png?width=1896&format=png&auto=webp&s=d624ff351c075f4bfea94fe42e8e85560566c8c6 * The **normal spray behavior**, where both viewpunch and recoil offset interact - (red) * The **recoil offset only**, with viewpunch completely removed - (blue) * The **isolated viewpunch component** \- (green) This direct comparison makes it visually undeniable: the jitter stems from the **recoil offset update rate**, not viewpunch. # Conclusion With this test, we just proved that what u/WhatAwasteOf7Years has been saying since the beta is in fact true: the jitter is caused by conflicting update rates between viewpunch (updated per frame) and recoil offset (updated per tick). Valve, this is now proven with data. It’s time to act. This issue affects one of the most important core mechanics of the game. # Acknowledgements Huge thanks and appreciation to u/WhatAwasteOf7Years who’s been saying this since beta—your hypothesis has been proven. This post is a tribute to your persistence. I learned a lot from our conversations as well. Stay well and glhf. **Original post by** u/WhatAwasteOf7Years **(2.5 years ago):** [Tick rate dependant recoil recovery causes poor spray](https://www.reddit.com/r/GlobalOffensive/comments/15evi1n/tick_rate_dependant_recoil_recovery_causes_poor/) Edit: u/SardineS\_\_ made a post with a video showing the erratic behavior of the "recoil" and i thing is an amazing job and a good supplement to both of my posts(this one and the previous one) link to the post: [https://www.reddit.com/r/GlobalOffensive/comments/1kqr14q/cs2\_vs\_csgo\_recoil\_video\_comparison\_why\_spraying/](https://www.reddit.com/r/GlobalOffensive/comments/1kqr14q/cs2_vs_csgo_recoil_video_comparison_why_spraying/)