WebLLM lets you run a real LLM entirely in the browser via WebGPU. No API keys, no server, no data leaving your machine. Just GPU inference on a 1.5B parameter model, client-side. The idea: what if instead of getting text back from a prompt, you got behavior? Write “be aggressive, chase enemies, never retreat” and the LLM spits out JavaScript that controls a tank in a battle arena.
How It Works
A tiny code-focused model (Qwen2.5-Coder 1.5B) runs locally in the browser and converts your natural language strategy into a JavaScript function body. That code executes every frame at 60fps, controlling your tank’s movement, aiming, and firing against 7 other AI tanks in a wrap-around arena.
Pipeline: prompt in, code out, validate, sandbox, execute.
Sensors
Each tank has up to 8 programmable sensors with configurable arcs, ranges, and offsets. Narrow 30-degree sniper sight, 360-degree paranoid coverage, whatever. The LLM configures them based on your prompt.
// "sniper" strategy generates something like:
tank.configureSensors([
{ arc: 30, range: 400, offset: 0 }, // narrow front sight
{ arc: 120, range: 150, offset: -90 }, // peripheral left
{ arc: 120, range: 150, offset: 90 }, // peripheral right
{ arc: 90, range: 100, offset: 180 }, // close rear warning
]);
The arena wraps like Asteroids. No corners to hide in, and enemies can be detected across wrapped edges.
Sandboxing
Running arbitrary LLM-generated code every frame is asking for trouble. A few layers handle this:
Validation strips markdown, function wrappers, and comments, then blocks forbidden patterns (eval, fetch, window, document, etc.).
Loop guards get injected at compile time. Code runs every frame so there’s no need for more than 100 iterations:
// while (condition) { body } becomes:
{ let __loopGuard0=0; while (condition) { if(++__loopGuard0>100)break; body } }
TankBrain wraps every API call in try-catch with fallbacks. getPosition() throws? You get {x:0, y:0} instead of a crash.
Auto-retry feeds errors back to the LLM with fix hints. Model writes enemy.distanceTo()? The retry tells it to use tank.distanceTo(enemy.position) instead, since enemy objects are plain data.
Prompt Engineering
Getting a 1.5B model to reliably output functional game AI required a specific system prompt: full API reference, two complete example strategies, common mistakes section, strict output rules (no function wrappers, no while loops, under 40 lines).
aggressive: "Be aggressive. Configure wide front sensors.
Chase enemies and fire constantly. Never retreat."
sniper: "Configure a narrow, long-range front sensor for precision.
Stay back, only fire at max gun range."
paranoid: "Configure 6+ sensors for 360° coverage.
Always know where enemies are."
Stack
Phaser 3 (Matter.js physics), React 19, WebLLM, TypeScript, Vite. Retro aesthetic from Hackernoon’s pixel icon library. Tauri support for desktop.
Play it here or check out the source on GitHub.