Two players, top-down dungeon, one rule: any room you're not looking at gets regenerated. Walk back the way you came and the corridors have moved.
The part worth talking about isn't the game, it's the constraint that makes it work: the client is never trusted. Every position, every hit, every room the dungeon reshuffles — the server computes it. The browser sends "these keys are down" and renders what it's told. Same reason you never trust a form field: if the client can assert it, a client can forge it.
Three decisions I'd make again:
- The simulation is pure functions, so it's unit-tested, not hoped at.
- The thing that ramps difficulty is weighted rules, not a model — it can't return a room that doesn't exist.
- The maze reshuffles as you move, but a spanning tree guarantees it stays solvable. I proved that with a test across 50 seeds, not a vibe.
Then I pulled all of it into a free, reusable pattern library, so the next build starts from the tested foundation instead of a blank file.