Snakes on a Screen: one evening, one prompt, one 3D FPS
A 5,000-line Three.js first-person shooter built in one evening using procedural geometry, Web Audio API synthesis, and zero external assets.
A Thursday night prompt — "zap a snake on a plane" — became a 5,229-line Three.js first-person shooter in under two hours. The build happened in three natural waves: a full-featured initial game with 18 source files, a UX overhaul that killed pretty-but-distracting clouds and redesigned controls for laptop trackpads, and a two-round visual iteration that evolved the snakes from floating sphere chains to continuous Catmull-Rom spline meshes with procedural scale textures. Every asset is generated at runtime — no PNGs, no MP3s, no 3D model files. The sounds are Web Audio API oscillator synthesis, the pilot announcements are Web Speech API through a bandpass filter, and the 90s CRT in-flight entertainment theme is pure CSS. The whole thing shipped to GitHub that same night.
Timeline
Started with a single sentence — "zap a snake on a plane" — and had a playable 3D first-person shooter running in the browser nine minutes after approving the plan. 18 TypeScript source files, a procedural airplane cabin with carpeted aisles and overhead bins, 5 snake species with different health pools and behaviors, a TAS-9000 stun gun with precision bolts and wide-angle EMP, a 3-stage campaign from Economy to Cockpit, and a Web Speech API pilot announcer delivering comedic PA lines through a bandpass radio filter. The whole thing runs on Three.js r170, zero art assets, zero external dependencies beyond the renderer. Every texture is procedural. Every sound is Web Audio API oscillator synthesis — taser crackles, airline chimes, spatial rattlesnake tails. The 285-line index.html is a full 90s CRT in-flight entertainment UI with scanline filters and a briefing screen. Pure prompt-to-game, no starter template, no boilerplate.
First playtest: the clouds were passing straight through the fuselage walls into the cabin. Pretty, but also pretty distracting when you're trying to spot a green viper under row 18. Killed the volumetric cloud geometry entirely — replaced the exterior with clean gradient sky planes visible through the window cutouts, and shifted the rendering budget to cabin detail: emergency floor strip lighting, seat headrest textures, galley cart geometry. The second problem was worse. Right-click to shoot? On a laptop trackpad? Nobody right-clicks fast enough to zap a rattlesnake mid-lunge. Remapped the whole control scheme: spacebar for precision taser, F for EMP shockwave, arrow keys for turning, C for crouching under seats. Now it plays like a game instead of a tech demo. The lesson: build the full feature first, then play it yourself and listen to what's annoying. The clouds looked great in screenshots. They were terrible in gameplay.
The snakes had heads, fangs, eyes, a forked tongue — and still didn't look like snakes. First pass: individual spheres strung together. Hard to distinguish from the cabin environment, hard to tell they were even reptiles. Second pass: viper skulls, belly scutes, hood geometry, two-tone coloring. Better anatomy, but now they looked like a parts catalog — a head here, a body segment there, all floating in the same general area. The fix took a completely different approach: throw away all the individual meshes and rebuild each snake as a single continuous CatmullRomCurve3 spline tube with procedural UV-mapped scale textures. Every vertebra follows the same smooth curve. The dorsal and ventral coloring wraps seamlessly around the tube cross-section. The tongue, fangs, and hood attach to the head control point, not as separate floating objects. 687 lines of Snake.ts to get a 3D reptile that actually slithers instead of jittering as a collection of disconnected geometry. Two rounds of "definitely an improvement, but..." before landing on the approach that worked.