Games / 2026 / Live in the browser
Heat Score
A third-person heist shooter in Three.js: three missions, guards that escalate from suspicious to combat, cover, grenades and a boss fight, plus an Android app that turns a phone into the controller by scanning a QR code.
01
The problem
A shooter lives or dies on its enemies. Guards that snap straight to firing feel unfair, guards that stand still feel dead, and both get worse in dense levels. This one also had to run on an integrated GPU with no downloaded models, textures or sounds.
02
How I approached it
Every collider is an axis-aligned box, so bullets, sight lines, camera collision and a 0.5 m A* nav grid all read the same list. Guards have a vision cone and hearing, and awareness climbs from calm to suspicious to alert to combat instead of flipping at once. In a fight they pick spots where crouching blocks your line of fire and standing restores theirs, fire in bursts under a cap on how many shoot at once, flank, and lob grenades at a player who camps one spot. Characters are primitives merged per body segment with vertex colour, so a rig with knees, elbows and faction gear costs about a dozen draw calls. Textures are drawn on canvas at load and every sound is Web Audio synthesis. The Kotlin controller pairs by QR code and streams sticks and buttons at 30 Hz through a WebSocket relay that runs inside the Vite dev server locally and as a Vercel Function when hosted.
03
The outcome
All three missions are checked end to end by scripted runs, and every objective, enemy and pickup is reachable on the nav grid from the spawn point. Merging character segments took the finale from about 610 draw calls to 394. On an Intel UHD iGPU, 17 dynamic lights cost about 15 ms a frame, so the default profile keeps four per level. The phone app has 6 unit tests for QR and address parsing.
Camera: this runs entirely in your browser. No video is uploaded or stored.