Princewill

Back to Blog
Backend

Building a Counter-Strike 2 Demo Analyzer with Node.js and TypeScript

July 2, 2026
6
#backend#nodejs#typescript#data-pipeline#counter-strike-2#demo-parser#algorithms
Building a Counter-Strike 2 Demo Analyzer with Node.js and TypeScript

For a technical assessment, I built a Counter-Strike 2 demo analyzer in TypeScript using @laihoe/demoparser2. The tool parses CS2 demo files, analyzes player deaths, calculates a surprise score based on player orientation and mouse movement, and generates ranked results for further AI analysis.

Building a Counter-Strike 2 Demo Analyzer

Recently I received a technical assignment to analyze Counter-Strike 2 demo files. I had never played CS2 before, so the challenge was not only writing the code but also understanding how demo files, ticks, player positions, and replay rendering work. The goal was to identify player deaths where the victim was likely caught by surprise. Later, those detected events would be rendered as videos and eventually used as part of an AI coaching pipeline.

Understanding CS2 Demo Files

A CS2 demo file (.dem) contains the complete replay of a match. Instead of storing a video, it stores the entire game state over time. Every few milliseconds the game records another tick. Each tick contains information such as: Player position, Camera direction, Weapon, Health, Events and Much more. This allows us to reconstruct any moment in the match.

Parsing the Demo

I used the @laihoe/demoparser2 package to read the demo file. Only the data required by the algorithm was extracted.

import { parseEvent, parseTicks } from "@laihoe/demoparser2";

const wantedProps = [
  "X",
  "Y",
  "Z",
  "pitch",
  "yaw",
  "steamid",
  "name",
];

const deaths = parseEvent(path, "player_death");
const ticks = parseTicks(path, wantedProps);

The parser produces two datasets. The first contains every player death. The second contains player information for every recorded tick.

Understanding Ticks

CS2 doesn’t record video. It records snapshots called ticks. Every player has information stored every tick. The algorithm can reconstruct the entire match from those snapshots.

Tick 187018

Player Position
Player View Angle
Weapon
Health
Movement

One second contains roughly 64 ticks, which is why the analyzer converts seconds into ticks when generating replay boundaries.

Architecture Diagram

CS2 Demo (.dem)

        │

        ▼

Demo Parser

        │

        ▼

Deaths + Tick Data

        │

        ▼

Analyzer

        │

        ▼

Ranked Candidates

        │

        ▼

analysis.json

        │

        ▼

Replay Validation

        │

        ▼

AI Coaching

Designing the Algorithm

For every death event the algorithm asks one question: Was the victim looking at the killer before dying? To answer that question it compares two things.

Mouse Movement

The algorithm sums the victim’s mouse movement over the last 128 ticks.

let yawMovement = 0;

for (let i = 1; i < victimTicks.length; i++) {
  yawMovement += angleDiff(
    victimTicks[i].yaw,
    victimTicks[i - 1].yaw,
  );
}

If the mouse barely moved, the player probably never reacted.

Viewing Angle

The direction from the victim to the killer is calculated.

const angleToKiller =
  (Math.atan2(dy, dx) * 180) / Math.PI;

The algorithm then measures how far away the victim was looking. Large angles mean the victim was facing somewhere else.

Final Score

The score combines both measurements.

const score =
  angleDifference * 2 - yawMovement;

Higher scores suggest the victim never noticed the attacker.

Improving the Output

Originally the JSON only contained the exact death tick.

{
  "tick": 187338
}

While testing the replay I discovered that jumping directly to the death tick often meant the victim was already dead.

I improved the output by adding clip boundaries.

const TICKS_PER_SECOND = 64;
const PRE_ROLL_SECONDS = 5;
const POST_ROLL_SECONDS = 2;

const startTick =
  Math.max(
    0,
    death.tick -
      PRE_ROLL_SECONDS *
      TICKS_PER_SECOND
  );

const endTick =
  death.tick +
  POST_ROLL_SECONDS *
    TICKS_PER_SECOND;

Now every candidate includes:

{
  "startTick": 187018,
  "tick": 187338,
  "endTick": 187466
}

Rendering clips became much easier because the replay starts several seconds before the interesting event.

Generated Analysis

The application outputs a ranked JSON file.

{
  "startTick": 187018,
  "tick": 187338,
  "endTick": 187466,
  "victim": "revoltz",
  "killer": "t9rnay",
  "weapon": "m4a1_silencer",
  "yawMovement": 14.68,
  "angleDifference": 168.28,
  "score": 321.88
}

The highest scoring events become the best candidates for manual review.

Validating the Algorithm

JSON

↓

Open CS2

↓

demo_gototick startTick

↓

Watch replay

↓

Compare gameplay against algorithm

Validating the Results

Writing the algorithm was only half of the task. I also needed to verify whether the highest scoring events actually represented surprising deaths. To do that I Opened the demo in Counter-Strike 2, Jumped to each startTick , Watched the replay, Compared what happened in the game with the calculated score. This validation step confirmed whether the algorithm was detecting meaningful situations instead of producing random numbers.

Video

Demo playback beginning at startTick showing one of the highest-scoring candidate events.

Challenges

The biggest challenges were not writing the algorithm itself. They included: Learning how CS2 demo files work, Understanding ticks, Setting up the replay environment, Debugging differences between macOS and Windows, Validating the algorithm against the actual replay.

What I Learned

This project taught me much more than reading game files. I learned how to: Build a complete data-processing pipeline, Analyze spatial data, Design scoring algorithms, Validate algorithmic output against real-world behaviour, Improve developer experience by generating render-friendly metadata, Although the project focused on Counter-Strike 2, the same engineering principles apply to many analytics systems beyond gaming.

Conclusion

This assignment was my first experience working with game analytics. By the end of the project I had gone from knowing nothing about CS2 demos to building a parser, designing a scoring algorithm, validating the results inside the game, and improving the output so that interesting events could be rendered automatically. Looking back, the most valuable part of this project wasn’t learning Counter-Strike 2. It was learning how to build and validate an analytics pipeline. The project started with parsing a game replay but ultimately became an exercise in transforming raw event data into actionable insights that can be reviewed by humans today and consumed by AI systems tomorrow.