Professional Counter-Strike 2 strategy is deep, structured knowledge — but it exists almost entirely in coach VOD reviews, Discord servers, and player intuition. There's no queryable, real-time format for "given that I'm on CT side, it's a force buy, and we're playing Mirage A site, what is the optimal setup?" StratForge AI is my answer to that question.
Why Deterministic Logic, Not a Machine Learning Model
The first architectural decision was the most important: how does the recommendation engine actually work? The obvious answer is "train a model on pro match data." The practical answer is that labeled professional match data at the granularity needed (round phase, buy state, exact positions, team composition, time remaining) is expensive to acquire, and an ML model without it would produce generic, untrustworthy recommendations.
Deterministic rule-based logic was the right call for v1. Professional CS2 strategy is already codified — the fundamentals of T-side default executes, CT holding positions, eco round behavior, and pistol round aggression are well-understood. Encoding these as explicit decision trees produces reliable, explainable recommendations that players can trust and understand.
- ›Game state inputs: map, side (CT/T), round phase (pistol/eco/force/full buy), time remaining
- ›Economy engine: tracks team and individual buy states, predicts opponent economy
- ›Strategy engine: matches game state to optimal strategy from a curated tactic library
- ›Adaptive layer: weights strategy suggestions based on round history and win/loss patterns
Radar Map Callout Overlays
One of the most technically challenging features was rendering accurate callout zones on CS2 radar maps. The problem: CS2 radar images are 1024×1024 PNG files, but the in-game world coordinates are on a completely different scale and origin. Simply overlaying a callout div at "A site coordinates" doesn't work — you need to remap world coordinates to pixel coordinates using the map-specific scale and translation values.
1// Mirage coordinate remapping (pos_x, pos_y, scale from radar.txt)
2const MIRAGE_MAP_CONFIG = {
3 pos_x: -3230,
4 pos_y: 1713,
5 scale: 5.00,
6};
7
8function worldToRadar(
9 worldX: number,
10 worldY: number,
11 config: MapConfig,
12 imageSize = 1024
13): { x: number; y: number } {
14 const x = (worldX - config.pos_x) / config.scale;
15 const y = (config.pos_y - worldY) / config.scale;
16 // Clamp to image bounds
17 return {
18 x: Math.max(0, Math.min(imageSize, x)),
19 y: Math.max(0, Math.min(imageSize, y)),
20 };
21}Each major map has a config object pulled from the game files containing the radar origin (pos_x, pos_y) and scale factor. Once the remapping function was accurate, I manually verified each callout zone by cross-referencing in-game screenshots against the computed overlay positions. The precision matters — a misaligned callout zone that puts "A ramp" on the wrong pixel is worse than no overlay at all.
Data Model and Team Profiles
Team profiles are backed by Prisma ORM with a PostgreSQL-compatible schema. The model tracks team composition, preferred strategies per map, round history, and win/loss records. Prisma's type-safe client made the database layer a joy to work with — zero runtime type errors from database queries because the TypeScript types are generated directly from the schema.
1// Prisma schema excerpt
2model Team {
3 id String @id @default(cuid())
4 name String
5 createdAt DateTime @default(now())
6 rounds Round[]
7 preferences MapPreference[]
8}
9
10model Round {
11 id String @id @default(cuid())
12 teamId String
13 map String
14 side Side // CT | T
15 buyState BuyState // FULL | FORCE | ECO | PISTOL
16 outcome Outcome // WIN | LOSS
17 stratUsed String?
18 timestamp DateTime @default(now())
19 team Team @relation(fields: [teamId], references: [id])
20}Testing and Deployment
Vitest covers the round outcome workflow — the highest-stakes code path where a bug would corrupt team history. Tests verify that round records are correctly persisted, win/loss counts update properly, and the adaptive recommendation weights shift appropriately after a sequence of round outcomes. The suite runs in milliseconds due to Vitest's native ESM support and Prisma mock injection.
Deployment on Vercel is automatic on push to main. The live application is at strat-forge-ai.vercel.app — the full stack runs on Vercel's serverless infrastructure with a managed Postgres database. Cold start times are acceptable for a tactical intelligence tool; this isn't a latency-critical real-time system.
💡 Takeaway: Domain-specific applications benefit enormously from encoding expert knowledge explicitly before reaching for ML. Deterministic logic is debuggable, explainable, and trustworthy in ways that a black-box model isn't — and for CS2 tactics, those properties matter.