Inspiration

I grew up on the top-down crime games. The whole city seen from above, sirens getting louder as you push it. Then Hotline Miami, where a room full of guys can kill you in a second and you can kill them right back just as fast.

What I wanted was a run. You get five minutes, it goes wrong somewhere in the middle, then it's over and you can see what everyone else did with the same five minutes. That suits a feed, nobody's opening Reddit for a 40-hour campaign. So it's one district, one clock, same district for everybody today.

What it does

Five minutes. A district of houses, some of them hits. Walk through a door and the roof peels off to show the crew waiting inside. Clear them, take their gun, and the next room is harder. Cops escalate with your wanted level and will bust you if you let the meter fill up.

Everything you break feeds one CHAOS score, which posts to a leaderboard that resets daily. The seed is the date, so everyone gets the same city on the same day. Clear every house before the clock dies and the district rolls over into a harder one with your score carried across, and the board shows the top ten plus your own position with an arrow for whether you went up or down since last time.

There's no tutorial. You get an ENTER arrow floating over a door and that's your lot. I'm still not sure that's enough for someone who lands on the post cold, and I ran out of days to find out properly.

How I built it

Devvit Web, Phaser 4 for the game, a Hono server on Redis for the daily board. TypeScript throughout, Vite for the build.

There's no tilemap for the buildings, they're drawn. Raised masses with beveled parapets and rooftop vents, built out of two or three overlapping boxes, so a block can come out as an L or a T or a U with a light well in the middle of it.

The interiors are data. Ten floor plans, each one just geometry: outline, rooms, doorways, where the crew stands. The outline is a real polygon, so the cross-shaped house is actually cross-shaped. That took a rewrite. My first version was a rectangle with the corners blacked out and it looked exactly as cheap as that sounds.

The CRT wash is Phaser 4's camera filters, colour matrix and vignette and barrel and bloom, with scanlines over the top. Same wash on the loading screen, the menu, the game and the results.

The sprites started out as a Python script that drew every frame procedurally, the player and the cops and a car I ended up never using and all the tiles, which I was pretty pleased with for about a week, and then near the end I deleted the whole thing along with the folder of source art it read from, so the PNGs sitting in the repo are now the only copies there are and there's no way back to regenerate any of them. Fine. Probably fine.

The menu is a live run playing behind the title, camera drifting around on a couple of sines.

Challenges I ran into

Two thumbs on a phone nearly sank the whole thing. Reddit's mobile browser kept killing the controls: second finger touches the glass, first thumb freezes mid-step. Not listening for touches at all and instead reading the screen fresh every frame fixed half of it. The other half was worse. pointerId comes back undefined in that webview, so the check I was using to tell one finger from another was comparing undefined to undefined and cheerfully matching every finger with every other finger. I lost hours to that convinced it was the movement code.

Enemies scraped along walls. They would hit a building between us and slide down it sideways instead of coming round. I tried teaching them to pick a better step and it didn't work, because a step is all they can see, and right next to a wall the only free direction is along the wall. They read the whole map before setting off now.

Ten hand drawn floor plans is too many to check by eye, which I found out by trying. So I built a page that draws all ten and tests them, walking a player sized body through every room from the front door and painting anything it can't reach in red. The first run lit up two rooms sealed off completely and two doorways that looked open but were too narrow to fit through. Later it flagged six layouts as broken that turned out to be a bug in the checker, which was a fun afternoon.

Phaser's barrel filter treats 1 as neutral rather than 0, which is how my first go at the CRT wash squeezed the entire game into a pinhole. Then setMask, which is what you'd use to clip a floor to an L shaped building, turns out to do nothing at all in Phaser 4's WebGL renderer. It doesn't error, it logs a warning and carries on, so the floor was spilling out into a corner that was meant to be solid.

The one I never got past: the devvit build plugin only does vite build, there's no dev server and no hot reload, so every single change is a full build and then a reload against a folder I was serving with python -m http.server on port 8099, which is fine the first ten times and then it's the four hundredth time and you're just sat there watching a progress bar for eight seconds to find out whether you moved a wall two pixels the right way. I never worked out if I was holding it wrong.

Everyone has to get the same city, which sounds obvious until you notice the engine rolls all its dice out of one cup. A single decorative roll (a rooftop vent, a splash of blood) shifts every roll after it and the enemies drift out of step with everyone else's copy of the day. Anything cosmetic rolls its own dice now.

Accomplishments that I'm proud of

The test page, more than any actual feature. I couldn't tell by looking whether the rooms were any good, and it kept turning things up long after I assumed it had run dry. The last one it caught was a doorway with a wall running straight through the middle of it, which looked completely fine on screen.

Buildings that aren't rectangles, too.

What I learned

Almost nothing here got found by me looking at it. The flood fill found the sealed rooms. The console caught the mask doing nothing. When I pulled the pathfinder out into a bare node script to time it, it had an infinite loop in it that grew an array until the whole thing threw, which had never once shown up while I was playing. And the six layouts I was sure were broken, I only worked out the checker was at fault by dumping the coordinates of every red pixel and finding they all sat in one line along the bottom edge, outside the building.

Not that more checks is automatically better. I wrote a "how cramped is this room" metric that scored 51 to 71% on every plan including the good ones, so it wasn't telling me anything. I binned it instead of fiddling with the thresholds.

The five minutes I'm still unsure about, two felt too short and five might sag somewhere in the middle, and I won't really know until there are enough scores on the board to see where people stop.

What's next for Neon Heist

Weekly seeds alongside the daily one, so there's something to grind at. Cars, which are still stubbed out in the code from an earlier version of this. The blood is still static splats and I never got round to animating it. And the obvious Reddit-shaped thing I didn't reach: letting people build the districts themselves. The floor plans are already pure data with a verifier attached, which is most of what you'd need to take layouts from players without shipping a house nobody can finish.

Built With

Share this project:

Updates

Submission history