PRJ-003 · games-interactive
Voidrunner
Personal — released on itch.io · Solo developer — design, code, audio · 2025
A browser endless-runner where the difficulty curve is a feedback controller: the game reads your recent deaths and near-misses and re-tunes obstacle density in real time.
Core Philosophy
Most endless runners are honest about only one thing: they want you to die. Voidrunner's thesis is that difficulty should be a conversation. A PID-style controller watches your last ninety seconds — deaths, near-misses, average reaction margin — and nudges spawn density toward a target frustration band.
The result reads as 'the game gets me'. Players in testing consistently described runs as fair even when they were objectively harder than the static baseline. That perception gap is the entire design: flow is a control-systems problem wearing a game's clothes.
Outcomes
- 4,100+ plays in the first month on itch.io
- Median session 7m 12s — 3× the genre's typical bounce
- Stable 60fps on integrated graphics via instanced obstacles
Deliverables
- Complete WebGL game
- Adaptive difficulty controller
- Original synthwave soundtrack loop
Technical Anatomy
Difficulty as a control loop
The controller treats 'player stress' as the process variable. Near-misses raise it slightly, deaths raise it sharply, clean streaks decay it. Spawn density chases a setpoint just below the player's failure threshold.
const error = targetStress - stress;
intensity += Kp * error + Ki * integral(error);
spawnInterval = clamp(BASE / intensity, MIN, MAX);
Instanced obstacle field
All obstacles are a single InstancedMesh with a ring buffer of matrices. Spawning writes a matrix; despawning swaps it with the tail. Zero allocations per frame, one draw call for the entire field.
Juice budget
Screen shake, hit-stop, and chromatic pulse all draw from a shared 'juice budget' so feedback never stacks into noise. Each effect requests intensity; the budget normalizes the sum under a ceiling.