A Ruby Sinatra web application that serves as the frontend/UI layer for the cs2-reviewer project. It provides a web interface to upload CS2 demo files, view parsed match data, and visualize kill positions on interactive map overlays.
web_server/
├── main.rb # Sinatra application (routes & logic)
├── Gemfile # Ruby dependencies
├── .ruby-version # Ruby 3.3.4
├── lib/
│ └── mod.rb # Empty module (placeholder)
├── views/ # Haml templates
│ ├── layout.haml # HTML layout wrapper
│ ├── index.haml # Upload form
│ └── upload.haml # Results display with map
├── public/ # Static assets
│ ├── css/ # Bootstrap 5
│ ├── js/ # Bootstrap + canvas-renderer.js
│ ├── maps/ # 9 CS2 map radar images
│ └── app/ # Alternative Vite+Svelte+TS frontend
├── spec/ # RSpec tests (minimal)
└── tmp/
└── boltobserv/ # Reference project (open-source CS2 radar)
# Install Ruby dependencies
bundle install
# Ensure the demo_parser binary is built
cd ../demo_parser
go build -o out/bin/cs2-reviewer ./cmd/cs2-reviewer
cd ../web_server# Development mode (auto-reload on file changes)
bundle exec rerun -- rackup
# Production mode
bundle exec puma
# Access the application
# Open http://localhost:9292 in your browserGET '/' - Landing page with file upload form
POST '/upload' - Main workflow:
- Receives uploaded demo file via multipart form data
- Calls Go parser binary:
../demo_parser/out/bin/cs2-reviewer - Captures JSON output from stdout
- Parses data with Oj (fast JSON library)
- Maps to Ruby Structs (DemoInfo, Team, Player, Round)
- Renders results with interactive map visualization
GET '/test' - Development endpoint (reads cached demo JSON)
GET '/app' - Serves alternative Vite+Svelte+TypeScript SPA
User Upload → Sinatra → Go Parser → JSON → Ruby Structs → Haml → HTML → JS Interactivity
- Browser-based interface (no command-line required)
- Upload .dem files directly
- Automatic parsing via Go backend
- Demo metadata (map name, server, duration in seconds/ticks/frames)
- Team information with player rosters
- Round-by-round breakdown:
- Round number, start/end times
- Winner and loser team IDs
- Winning reason code
- Complete kill feed
- Click on any kill event to visualize player positions
- Colored dots on map overlay:
- Red = Victim position
- Blue = Attacker position
- Green = Assister position (if exists)
- Supports split-level maps (Nuke, Vertigo with upper/lower floors)
Includes radar images and coordinate metadata for:
- de_ancient
- de_anubis
- de_dust2
- de_inferno
- de_mirage
- de_nuke (split-level)
- de_overpass
- de_train
- de_vertigo (split-level)
Coordinate Transformation: CS2 uses 3D world coordinates (X, Y, Z in game units). These are converted to 2D SVG coordinates using:
posX = (offsetX + x) / ratio
posY = (offsetY - y) / ratio // Note Y-axis inversionEach map has specific transformation parameters stored in public/js/canvas-renderer.js.
Split-Level Maps: Nuke and Vertigo have upper/lower levels. The Z-coordinate determines which transformation to use:
- Nuke: Z < -480 = lower level
- Vertigo: Z < 11680 = lower level
Web Framework:
sinatra- Lightweight web frameworksinatra-contrib- Sinatra extensionsrackup- Rack server interfacepuma- Production-grade multi-threaded web server
Templating & Parsing:
haml- HTML abstraction markup languageoj- "Optimized JSON" - fastest Ruby JSON parser
Development:
rerun- Auto-restart server on file changeszeitwerk- Modern code autoloader
Testing:
rspec- BDD testing framework
Ruby version: 3.3.4
- Backend: Ruby 3.3.4 + Sinatra + Puma
- Parser: Go binary (separate component in ../demo_parser)
- Frontend: Bootstrap 5 + Haml templates
- Interactivity: Vanilla JavaScript with SVG manipulation
- Assets: CS2 map radar images (sourced from Boltobserv project)
The application uses Ruby Structs for lightweight data modeling:
DemoInfo = Struct.new(:mapName, :serverName, :durationInSec, :durationInTick, :durationInFrame)
Team = Struct.new(:id, :name, :players)
Player = Struct.new(:steamID, :gameID, :name)
Round = Struct.new(:roundNumber, :startTime, :endTime, :winningReason, :winnerId, :loserId, :killFeed)Note: These Structs simplify the full Go output, omitting financial data and detailed team states for display purposes.
An experimental Vite + Svelte + TypeScript SPA is in development under public/app/. This represents a more modern, reactive UI approach alongside the traditional server-rendered templates.
Access it via: http://localhost:9292/app
Status: Incomplete/experimental
The tmp/boltobserv/ directory contains a reference copy of Boltobserv, an open-source CS2 observer radar tool (GPL-3 licensed).
Purpose:
- Source of high-quality map radar images
- Reference for coordinate transformation systems
- Inspiration for advanced visualization features
The map assets in public/maps/ and coordinate metadata in canvas-renderer.js are derived from this project.
Bugs:
/testroute has syntax error on line 60 (missing=)
Missing Features:
- No error handling (Go binary failures, corrupt demos, wrong file types)
- No file validation (any file can be uploaded)
- No loading indicators (parsing large demos blocks synchronously)
- No persistence (demos not stored, no history)
- No way to clear map circles (persist until page refresh)
- No zoom/pan functionality on maps
Incomplete:
lib/mod.rbis an empty placeholder- Svelte SPA incomplete
- Minimal test coverage
- Generic boilerplate in original README
Data Loss:
- Ruby Structs drop financial stats and detailed team states from Go output
- Only basic round/kill data is displayed
Limitations:
- Hardcoded map metadata (code changes required for updates)
- Only 8 maps have transformation metadata (de_train missing)
- No coordinate bounds validation
- Hardcoded relative path to Go binary
This web interface enables:
- Browser-based demo analysis - No command-line required
- Match review - View teams, rosters, round outcomes
- Kill position analysis - Visual representation on actual maps
- Spatial analysis - Understand engagement locations
- Post-match breakdown - Review timing, round flow, statistics
Perfect for CS2 players and teams wanting accessible demo review tools.
bundle exec rspec- ✅ Core functionality works (upload → parse → display → visualize)
⚠️ Missing production polish and error handling- 🚧 Exploring multiple frontend approaches (Haml + Svelte)
- 🚧 Mid-development based on git history
The web server calls the Go demo parser as a subprocess:
stdout, _stderr, _status = Open3.capture3('../demo_parser/out/bin/cs2-reviewer', tempfile.path)Requirements:
- Go binary must be built and present at
../demo_parser/out/bin/cs2-reviewer - Binary must output valid JSON to stdout
- Synchronous execution (blocks until parsing completes)
This project uses assets and coordinate systems derived from Boltobserv, which is licensed under GPL-3.