← Devblog

The house gets its glow

For its whole life so far this game has been deliberately ugly — flat placeholder rects, because the one question that mattered (“is being the bait fun?”) had to be answered without polish flattering it. That question is answered. The moratorium is lifted. Behold, the same room, before and after:

The old look: flat dark grey tiles, plain colored squares for player and zombie, no effects of any kind.
Before: the honesty era. Informative the way a spreadsheet is informative.
The new look: the same room through a CRT composite — scanlines, film grain, vignette, chromatic fringing on the white player square, warm amber intercom text at the bottom, cool slate walls over a near-black floor, a rot-green zombie, and a dark red pulsing exit.
After: scanlines, grain, vignette, a little chromatic fringe on anything bright, and an intercom that now glows the warm amber of a man who is delighted you took the job.

The whole frame — board, HUD, narration, newsletter — renders to an offscreen target at a fixed internal resolution, then passes through one full-screen shader on the way to your monitor: scanlines, film grain, edge-weighted chromatic aberration, a vignette, and a faint phosphor glow. It’s the cheapest possible version of the look, on purpose; the render-target plumbing it needed is the exact infrastructure a certain future season’s lighting will reuse, so this pass paid for groundwork, not just vibes. Press C if you want it all off — the effects are a coat of paint over the same pixels, and the game will not pretend otherwise.

The palette got its first deliberate draft too. The sitter is the only clean white thing in the house. The kids idle in a grey-green that no living thing has ever been, flush an alarming red when they’ve chosen you, and settle into a sleepy blue once you’ve put them to bed — three states a glance can’t confuse, because this is still a routing puzzle and style never outranks being able to see what’s about to eat you. The kids also shamble now — a small per-child wobble, each on their own phase so the household doesn’t march in step. It’s cosmetic, drawn-position-only; the simulation underneath is the same deterministic clockwork, and all fifty tests neither noticed nor cared.

The robot’s second trophy

Rendering to an offscreen target immediately crashed the page in headless Chrome — and this one’s genuinely obscure: the graphics library’s render-target constructor defaults its multisample count to 1, but the code that decides whether to run a WebGL2-only resolve step checks for “not zero” instead of “more than one.” So plain, non-MSAA usage — the only kind we use — walks into a WebGL2 code path on a WebGL1 context and dies on a function that doesn’t exist. Passing an explicit zero sidesteps it for an identical result. That’s the second real upstream bug this project has hit in one week of using the engine’s web path, which is either bad luck or evidence that the smoke gate — which caught both before any human did — is the best hire this project has made. (It also had to get honest again: film grain animates every frame, which would have let its “does input actually move the player” check pass forever without proving anything. It now switches the effects off before running that check. A test that can’t fail isn’t a test, third verse.)

Numbers: 761 KB of WebAssembly, up ten from the shaders — the entire look costs less than one photograph of it.