Cloud Radar
Most game radars are overlays. You run something locally, it reads memory, it draws dots on top of the game. Works fine if it's just you. Bulky if you want to share what you're seeing with anyone else.
I wanted something I could give a session ID to and have a friend open in a browser. No overlay on their end, no injection, no install. Just a WebSocket and a canvas.
How it works
Three pieces:
- An AngelScript script inside Perception that reads CS2 memory.
- A Bun server that relays packets and tracks sessions.
- A SvelteKit page that renders the radar.
Game side
When the script attaches to cs2.exe, it pattern scans for the entity list and position offsets, loads CS2's schema for the struct definitions, and spawns three threads:
- PlayerCache at 100 ms – walks the entity list and builds a buffer of valid players.
- PlayerInfo at 16 ms – reads position, health, and armour for each one.
- RadarStream at 16 ms – serialises and ships.
The bandwidth saver is the binary protocol. JSON would be {"players":[{"x":123.4,"y":456.7,...}]} for every player every frame – that gets fat fast. Instead:
- 9-byte header: message type, player count, timestamp.
- 16 bytes per player: X/Y/Z (12), health (1), armour (1), padding (2).
For a 64-player server at 60 fps that's about 1 KB a frame. JSON is 10–20× that.
Thread safety in AngelScript was annoying. No real concurrency primitives. I ended up using a triple-buffer – one buffer fills while another is read, atomic swap between them, third stays idle. No locks, no races.
Server
Bun, TypeScript. It does sessions (each stream gets an ID, viewers join with that ID), API key validation for game clients, viewers get the session ID and that's it. It parses both CS2 and ARC binary formats and routes them to the right handlers, broadcasts to viewers, and tracks dirty players in MongoDB for the cheater report flow.
HTTP and WebSocket on the same port. Rate limiter on by default.
Frontend
SvelteKit page. The radar is a 2D canvas with the map as a background and player dots that interpolate between updates so it looks smooth even when a packet is late. Player cards on the side show health, armour, weapon, and Steam ID. There's a session UI for generating or joining a code.
Tailwind and shadcn-svelte for the chrome. Dark theme to match the game.
What I'd do differently
Bun's native WebSocket is the reason this works at the throughput it does – it handles the message rate without any tuning. The triple-buffer was the right call; I tried a single buffer with a mutex first and the script stuttered.
If I redid it I'd probably swap the polling-style read threads for an event-driven hook. Right now I'm sampling at 16 ms which is plenty for 60 fps but wasteful at lower frame rates.