Cloud Radar: Real-Time Game Data Streaming
Most game radars are overlays — you run something on your local machine, it reads memory, draws some dots. Works fine if you're alone, but it's bulky, easy to detect, and you can't share the data with anyone.
I wanted something cleaner. A lightweight script that runs on your machine, extracts game data, sends it to a server, and anyone with a session ID can view it on their browser. No overlay, no injection, just WebSocket packets.
How It Works
The system has three parts: a game-side script that reads memory, a server that relays data, and a web UI that displays it.
Game Client (AngelScript)
The script runs inside Perception and handles memory reading. When you start it:
- Attach to the game process (cs2.exe)
- Pattern scan to find key memory offsets (player list, position data, etc.)
- Load schema data for CS2's struct definitions
- Spawn three threads that run in parallel:
- PlayerCache (every 100ms) — scans the entity list, builds a buffer of valid players
- PlayerInfo (every 16ms) — reads position, health, armor from each player
- RadarStream (every 16ms) — serializes to binary and sends it to the server
The key to efficiency is the binary protocol. Instead of sending JSON like {"players": [{"x": 123.4, "y": 456.7, ...}]}, we pack everything into raw bytes:
- 9-byte header: message type, player count, timestamp
- 16 bytes per player: X/Y/Z position (12 bytes), health (1 byte), armor (1 byte), padding (2 bytes)
For a 64-player server at 60fps, that's roughly 1KB per frame. JSON would be 10-20x larger.
Thread safety was the tricky part. AngelScript doesn't have great concurrency primitives, so I used a triple-buffer pattern — one buffer gets filled while another gets read, with an atomic swap between them. Prevents race conditions without locking.
Server (Bun + TypeScript)
The backend is a Bun server running on Node. It:
- Manages sessions — each radar stream gets a unique session ID that viewers use to join
- Validates API keys — game clients must authenticate; viewers just need the session ID
- Parses binary data — supports CS2 and ARC formats, routes them to the right handlers
- Broadcasts to viewers — all connected browsers watching a session get the data in real-time
- Tracks dirty players — MongoDB-backed system for reporting suspected cheaters
The server handles both HTTP (for the SvelteKit app and REST endpoints) and WebSocket upgrades on a single port. Rate limiting is baked in to prevent spam.
Frontend (SvelteKit + Canvas)
The web UI is a SvelteKit app with:
- Canvas radar — renders a 2D top-down view of the map, with player dots that interpolate smoothly between updates
- Player info cards — shows health, armor, weapon, and Steam ID for each player
- Session UI — generate a new session or join an existing one with a code
- Dark theme — Tailwind + shadcn-svelte, styled to match the game
The renderer handles coordinate transformation from game-world units to canvas pixels, and since we're getting binary updates every 16ms, interpolation looks smooth even when packets get delayed.
Lessons Learned
Binary formats are worth it. JSON over WebSocket would've been massive bandwidth. We're talking 10-20x difference. At 60fps with 64 players, that matters.
Thread safety in AngelScript is annoying. There are no mutexes, so the triple-buffer approach (atomic swaps between read/write buffers) is the way to go.
Bun is fast. The native WebSocket support handles hundreds of binary messages per second without breaking a sweat. No fancy framework code, just raw speed.
SvelteKit + Canvas works well together. Svelte's reactivity handles state changes, Canvas handles the heavy rendering. Clean separation of concerns.
For educational and security research use.