Commercial title · Steam · September 1, 2026
A story-rich arcade brick-breaker. One hundred and thirty-two levels, each one a memory in a family's century, told by four narrators over an original 36-song score. Every board is proven completable by a machine before a player ever sees it.

Each level opens with a memory. Play it or skip it — the bricks are waiting either way.
The shape of the thing
Classic paddle-and-ball play, rebuilt around a family's whole century — one memory per level, told in four voices, sealed against something the game calls the Fog. It ships as a native desktop application on macOS and Windows, with a free demo and a separate soundtrack release, all three wired into Steam as distinct storefront products.
The content is not decoration. 132 hand-designed boards, 179 individually rendered voice files across four narrators, a 36-song score curated track-by-track against the story it plays under, 33 achievements, and four paintings revealed brick by brick behind the four bosses.
A level you can't finish is a bug you ship to everyone
In a brick-breaker, bricks can enclose other bricks. Get the geometry slightly wrong and a destructible brick ends up sealed behind indestructible ones, unreachable by any ball on any trajectory. The player doesn't get a crash or an error — they get a level that simply never ends, and they quit. Games in this genre ship this bug regularly, because catching it by hand means playing every board to completion after every edit, and nobody does that 132 times.
I built a formal winnability prover. It pads the board with an open border, flood-fills all reachable empty space, destroys every destructible brick adjacent to that space, and repeats until nothing changes. If any destructible brick remains sealed, the level is unwinnable. It runs as an automated test across all 132 boards on every single edit.
The board file's own header states it as a design contract: every level is proven winnable by the peel algorithm. It isn't a QA pass anyone can forget. It's a build obligation the content has to satisfy.


Not from how it felt on a Tuesday
I architected the game core to be genuinely renderer-free — no graphics library imported anywhere in the simulation. That was a structural decision made early, and it paid for itself immediately: it means the game can be played by something that isn't a person.
Two headless bot harnesses do exactly that. The first plays every round 100 times independently and reports mean, median, and 10th/90th-percentile paddle hits, plus simulated seconds, mean deaths, and stalls — so per-board difficulty reads with no single-playthrough luck in it, and a second pass detects boards that evaporate under a maxed power-up. The second plays the entire 132-round campaign end to end for score calibration, distinguishing peak score from final score because continues reset the counter.
The difficulty curve is a dataset, not an opinion.


Release engineering
Development builds have cheat keys. Released builds must not, and "remember to turn it off" is not a control. So the cheat keys compile only when the dev server is running — they derive from the build command itself, meaning every distributable build is clean by construction, with no flag anyone could forget.
That's backed by two independent fail-closed checks in the build script. Before compiling, it greps the config to confirm nobody has tampered with that derivation, and aborts if they have. After compiling, it greps the built bundle's actual bytes for a sentinel string and aborts before code signing if it finds one. Two different failure modes, two different checkpoints, both refusing rather than warning.
Defects are tracked in a file with real IDs, a severity lattice, and a stated ship gate: no critical or high-severity bug may be open when the game ships. It also carries a rule I'd put in any team I ran — fixed is not verified. Code landing marks a bug fixed and unverified; it isn't done until a test or a human in the real build proves it. That rule exists because a "fix" once shipped unverified and was still broken two builds later.


Where most of the engineering actually went
Level art round-trips through a spreadsheet. I design boards by filling cells with color in Excel, which is a genuinely good level editor if you don't tell anyone. A script reads the cell fills into game data; a second script exports any of the 132 boards back out into that exact spreadsheet format, palette legend and all. Edit in either direction.
Narration is a three-stage, resumable, idempotent chain. The voice model truncates the final word of roughly 40% of renders, so every line is rendered with a throwaway tail — the truncation lands on scaffolding, never on the script. A second stage finds each clip's true endpoint by RMS waveform analysis at 20-millisecond frames. A third physically truncates the shipped audio at frame boundaries, losslessly, backing up originals on first run so the pass can be run again safely.
Nine live design tools run on a second dev server — a level auditor, a script reader with true-rate playback, a drag-and-drop album arranger, an icon studio. They import the game's own modules at load time rather than reading snapshots, so they cannot drift out of date with the thing they're reviewing.


Because a portfolio that only flatters is worth nothing
FIRST LIGHT began as a fresh-history fork of a finished Arkanoid tribute I'd built the week before — same engine, new game. Twenty-two days of commits on this repository is real, but it isn't twenty-two days from nothing, and I'd rather say so than let the number do work it hasn't earned.
There is no CI. The test suite is a human ritual enforced at the build script rather than by a server, which is a defensible call for one person on two machines and would be the wrong call on a team. Git tagging lapsed partway through the release series. One medium-severity bug is open at the time of writing — intermittent, no known reproduction — and it's in the tracker where I can see it.
The art, music, and narration are AI-generated from my direction, under a commercial license, disclosed on the Steam store page in language I revised downward when an earlier draft overclaimed my role.