A stealth game on a $30 development board. Tilt it to move, tap to throw a sound bomb, and hold a finger down to see where the guards will walk. A hundred stages, ranked by how hard they measurably are, running at fifty frames a second on a microcontroller.
Sound is the currency
Guards walk patrols across a tile maze, sweeping vision cones ahead of them. You have to free every hostage and reach the exit without ever being fully seen. The premise comes from Stealth, a realtime-tactics puzzler on Steam. This version is rebuilt around the one input a handheld board has that a desktop game doesn’t: which way you’re holding it.
Tilt is the joystick, and speed is continuous. A slight lean creeps; a hard lean sprints at 205 pixels a second, and past about seventy percent of full tilt your footsteps start to carry. Noise doesn’t radiate in a circle. It flood-fills through open floor, so it rounds corners but never passes through walls, and that makes a sound bomb a real tool: one thrown down a side corridor genuinely pulls a guard away from the door it was watching.
Detection is a meter, not a tripwire. Being clipped by the edge of a cone for a moment is survivable; standing in the middle of one is not. The meter traces the outline of the panel itself, starting at the bottom, running up both sides and closing at the top at the instant you’re identified, so it reads in peripheral vision without looking away from the guard. The heartbeat in the speaker climbs with it. And once you’re seen, running won’t save you: a chasing guard moves at least as fast as you do, so the only way out is to break line of sight.
| Tilt the board | Move. The steeper the lean, the faster, and the louder. |
| Tap the field | Throw a sound bomb where you tapped. |
| Press and hold | Reveal every guard’s patrol route. |
| BOOT button | Pause: resume, re-level the tilt, restart, or quit. |
Two initials, for the records
Everything you need to know
Stage 100: rescue four
Hold to see every route
A bomb, and a guard who heard it
A cone goes hot; the meter climbs
Last hostage: the exit opens
Cleared, with a new record
The button pauses anywhereWaveshare ESP32-S3-Touch-AMOLED-1.8 · $29.99
It’s the same board as AI Pixel Cat, for the same reason: everything a handheld game needs is already on it. A bright AMOLED panel, touch, an accelerometer, a speaker, a button, and flash to keep records in. There’s no wiring and nothing to add.
| Hardware | Part | What it became |
|---|---|---|
| Display | 1.8″ AMOLED, 368×448, over QSPI | The whole play field, redrawn every frame. The alert meter follows its rounded corners. |
| Motion | QMI8658 6-axis IMU | The joystick. Tilt is read from the accelerometer. |
| Touch | Capacitive, FT3168 or CST816 | Tap to throw, hold to reveal routes, and every menu. |
| Audio | ES8311 codec and speaker | Alarm, heartbeat and music, all synthesised live. |
| Button | BOOT, on the side | The pause menu, on any screen. |
| SoC | ESP32-S3R8, 8 MB PSRAM, 16 MB flash | 449 KB of firmware. Frames are drawn in the chip’s internal RAM. |
| Storage | Flash | Your initials, and the best time on every stage. |
Two revisions of the board ship under the same SKU, with different display and touch chips. The firmware probes for the touch controller at boot and adapts, so one binary runs on both.
A complete game on one board
We defined Stealth Game around the hardware: a complete handheld game with no wiring or extra controls. Tilt had to feel natural, touch had to make sound bombs easy to aim, and the display and speaker had to make danger clear without pulling attention away from the maze. The game needed a hundred stages with a measured difficulty curve, responsive movement, and records that survive a power cycle.
Those requirements shaped the engineering rules:
The safety net is one command, ./tools/check.sh, which runs six suites in a few seconds with no board attached: the stage tables, input and menu geometry, patrol-route rendering, a smoke test of all hundred stages, a long numerical soak, and tilt filtering.
Where a 19 millisecond frame goes
The whole 368×448 panel is redrawn every frame by a software rasteriser. There’s no GPU and no sprite hardware. The obvious design, double-buffered frames in the board’s 8 MB of PSRAM, didn’t survive contact: DMA reading one frame while the CPU draws the next saturates the PSRAM bus, and the display link underruns. So the frame is drawn in horizontal bands in the chip’s own internal RAM, two small buffers ping-ponging, one being drawn while the other streams to the panel.
One frame on a five-guard stage, measured on the board
The lesson
Every band costs a synchronous round trip to the panel before its pixels can stream. Going from fourteen bands to four took the game from 34.5 to 50 frames a second, more than every CPU optimisation combined. The frame is now limited by the bus, not the processor, and the one lever left is the display clock, which can only be judged by looking at the panel.
No samples, everything synthesised
There are no audio files in the firmware. An alarm, a heartbeat, a music bed and a handful of effects are synthesised live at 22 kHz on the chip’s second core. The game and the synth never share a lock, so a slow frame can never stall the sound.
The first version made the obvious mistake: a 36 Hz drone and a 62 Hz heartbeat thump, which sound great on headphones. On the board, nothing. The speaker rolls off hard below about 300 to 400 Hz, and almost all of the music’s energy sat below 250. The fix borrows a trick from psychoacoustics: every low sound is built from a fundamental plus its second and third harmonics. The harmonics land where the speaker can move air, and the ear fills in the missing fundamental.
Share of the music’s energy the speaker can play (above 300 Hz)
Listen
The music bed, then the heartbeat as tension climbs from calm to caught, rendered by the same synthesiser code the board runs.
The detection alarm is the loudest sound in the game, and it fires on the exact frame a guard’s cone turns hot. One flag drives both the colour and the sound, so they can never drift apart.
Difficulty is a model, not a guess
All hundred stages are generated, and the generator’s real job isn’t making mazes. It’s knowing how hard each one is. Guard count, hostage count and wall density are the obvious knobs, and on their own they’re weak predictors. What gets scored is how hard a stage is to actually move through:
The one hard rule
No stage can be finished without crossing ground a guard watches. The player chooses the route, and will choose whichever keeps its worst moment lowest, so the generator solves for exactly that: for every pair of objectives, the route whose most-watched tile is least watched, tried over every order of rescuing the hostages. If that number is zero, there’s a way through that never enters a cone, and the stage is thrown away.
Averaging exposure along one chosen route wouldn’t catch it. A stage can look dangerous on the direct path and still have a completely safe detour beside it.
The generator is deterministic, so regenerating reproduces the same hundred stages byte for byte. Records are keyed to a fingerprint of the stage table, so a new set of stages can never quietly inherit the old set’s best times.
C, ESP-IDF 5.5, 449 KB of firmware, MIT licensed
Everything is in the repository: the game, the stage generator and its difficulty model, the test suite, and the harnesses that render frames and audio without a board, including the recorder that made the video at the top of this page. A bootstrap script installs the toolchain, builds the firmware and runs the tests on a fresh Mac.