Inspiration

Wordle proved something simple. Give everyone the exact same puzzle each day, let them share how they did, and the thing spreads on its own. The magic is not the puzzle. It is that your result is comparable to everyone else's, so a score becomes a conversation.

I wanted that hook around a real game instead of a word grid. A rogue-like where the entire subreddit crawls the identical dungeon for 24 hours, races for the deepest run, and argues about the brute on floor 7 in the comments. On Reddit that turns into something no standalone daily can do: the leaderboard, the audience, and the water cooler are all the same place. The subreddit is the game's arena.

The second seed of the idea was a constraint I set myself. Ship a full action game inside a single Reddit post, with no asset pipeline at all. Every pixel and every note generated by code. That constraint is what gave the game its identity.

What it does

Chrono-Crypt is a daily rogue-like dungeon crawler that lives inside a single Reddit post. Every midnight (UTC) the Forgotten God rebuilds the maze. For the next 24 hours everyone in the subreddit drops into the same crypt: the same layout, the same monsters, the same three daily twists. There is no way to win. You descend as deep as you can before you die, and the deepest delvers top that day's leaderboard.

When you fall, the crypt hands you a Death Card: how each floor went as an emoji strip, the named enemy that killed you, the percentage of today's delvers you outlived, and your daily streak. One tap posts that card to the subreddit as your own post. Because everyone crawled the same crypt, "floor 9, the brute on 7 got me" actually means something to the person reading it.

Underneath the daily loop:

  • Turn-based combat so you always have time to think, layered over enemies that patrol in real time and hunt you if you idle.
  • A bow you find mid-run, for firing down a row or column at limited ammo.
  • Story rooms where a choice (kind, cruel, or a gamble) has real trade-offs, and the whole subreddit's choices nudge the next day's world mood.
  • Gravestones of the players who fell on the exact floor you are standing on, each with an epitaph. Light, asynchronous presence on a shared map.
  • Opt-in difficulty Seals and daily modifiers that raise the stakes and the score multiplier.
  • Streaks with a weekly mercy that forgives one missed day, and flair titles earned through play.
  • An always-open Boss Gauntlet: an endless mode with a boss on every floor and a power-up to pick each time you clear one, on its own all-time leaderboard.

Once installed, it also runs itself. A fresh crypt posts every midnight and yesterday's crypt gets a pinned community post-mortem.

How we built it

Built on Reddit's Developer Platform (Devvit Web) with Phaser 4 on the client, Hono on the server, Redis for state, and TypeScript end to end. The repo is split into client/ (the Phaser game and the in-feed splash), server/ (Hono routes, the daily engine, persistence), and shared/ (pure, DOM-free game logic imported by both sides).

Determinism is the whole architecture

The "everyone plays the same crypt" promise is not a feature bolted on top. It is the constraint the entire codebase is organised around.

The date is turned into a seed with FNV-1a over the string chrono-crypt:${YYYY-MM-DD}, giving a stable 32-bit unsigned integer. That seed drives a mulberry32 PRNG, a tiny, fast generator with a good enough distribution for level generation. Math.random() is banned anywhere the result has to match across players.

Each floor mixes the day seed with its depth using a golden-ratio-derived constant, makeRng((seed ^ Math.imul(depth, 0x9e3779b1)) >>> 0), so floor N is identical for everyone today but a different shape from floor N yesterday. The Gauntlet uses a different mixing constant (0x85ebca77) so its floors never alias onto the daily's.

The payoff is that the same generation code runs in three places: the game client, the server, and the in-feed splash preview. They all seed from the day, so every player and even the post thumbnail land in the identical dungeon, with zero coordination between them. The server can also regenerate any floor to validate a submitted run, because generation is a pure function of the seed. This is why all of shared/ is kept strictly side-effect-free and DOM-free.

Procedural level generation

A day gets a distinct shape, not just a distinct seed. The base 25x19 generator picks one of three archetypes from the RNG:

  • Rooms: rectangular rooms placed with rejection sampling, then joined centre-to-centre with L-shaped corridors. Connectivity is guaranteed by construction.
  • Caverns: cellular automata. Start at 45% walls, run four smoothing passes (a cell becomes wall if five or more of its eight neighbours are), then keep only the largest 4-connected floor region via flood fill. Start and exit are the region's seed cell and its BFS-farthest cell, so they are always mutually reachable.
  • Maze: a recursive-backtracker carve into one-wide corridors, fully connected by construction, exit again placed at the BFS-farthest cell.

On top of that, the Daily uses a Binding-of-Isaac-style room graph: up to nine 13x9 rooms grown randomly outward on a 5x5 cell grid, doors opened between orthogonally adjacent rooms, and the exit room chosen as the graph-farthest from the start by BFS over the room adjacency. Doors stay sealed until a room is cleared, so progress is gated on combat, not exploration. One guaranteed big open "arena" floor per day is pinned to a seed-derived depth (2 + seed % 7) so every player hits it on the same floor, with a rare (~7%) random arena on top for variety.

The Gauntlet reuses the same primitives as either one large boss arena or, from floor 2 on with a one-in-three chance, a short linear chain of rooms each guarded by its own boss, so you fight through every guardian in sequence to reach the exit.

Bosses scale on two curves. Daily guardians (every fifth floor) stay soft: hp = 36 + depth*3 + depth^2*0.35. Gauntlet guardians climb hard in both HP and attack: hp = 36 + depth*5 + depth^2*0.7, atk = 4 + floor(depth/2), so the endless mode keeps producing real walls.

No assets, on purpose

There is not a single image or audio file in the project.

All art is drawn from tiny hand-authored pixel maps (an 8x10 grid of palette-index glyphs, . for transparent) rendered into Phaser textures at 6px per cell at runtime. Wall and floor tiles are authored in grayscale and tinted per biome, so one tile set becomes any of eight colour worlds. Every sound and the music are synthesised with the Web Audio API (oscillators plus gain envelopes, a lazily-created AudioContext resumed inside the first user gesture to satisfy autoplay policy). This also sidesteps Devvit's content-security policy on external assets, and it keeps the whole game tiny.

The trailer was built the same way: a canvas cinematic with a fully procedural chiptune score, no footage and no stock music.

Persistence and the daily engine

The server is a set of Hono route groups mounted under /api, /internal/menu, /internal/scheduler, and /internal/triggers. State lives in Redis under a deliberately flat key schema:

  • leaderboard:{date}: a sorted set, member = username, score = best run that day. A player's zScore doubles as both their rank entry and their personal best, so no separate per-user key is needed.
  • gauntlet:leaderboard: one global sorted set. Because it ranks floor first and score second, both are packed into a single numeric key as floor * 1_000_000 + clamp(score), then unpacked on read.
  • played:{date}:{user}, streak:{user} / streakLast:{user} / streakWard:{user}:{week}, clears:{user}, dailyPost:{date}, deaths:{date} (a JSON list capped at 60), killers:{date} (a hash tallying which enemy has claimed the most delvers), and cumulative community:* counters that drive shared narrative Chapters at 10k / 50k / 150k kills.

The daily rollover is a cron task (0 0 * * * in devvit.json). It opens the new day's crypt as a fresh titled post, records its post id, then seals yesterday's post with a pinned community post-mortem comment (top delvers, most feared killer, world mood, chapter progress, and a link to today's crypt). Pinning is best-effort: the comment still lands even if the app account cannot distinguish. The same function backs a moderator "test" menu item so the whole cycle can be staged on demand. This is the self-refreshing content engine that makes the game a daily habit rather than a one-shot.

Sharing without trust

The Death Card share is a POST /api/share. The card text is rebuilt on the server from the run facts, with the percentile and streak computed server-side, so a client cannot forge a better card. Incoming floor marks are filtered against a known set and length-bounded so a crafted payload cannot bloat the post. The card is then published as the player's own post via reddit.submitPost with runAs: 'USER' and an outcome flair, and the response hands the client a direct link to jump to. Posting tries hardest-to-easiest (user with flair, user, app with flair, app) so a card always lands even if user-action permissions are not granted on a given subreddit.

Challenges we ran into

Determinism across three surfaces. A single ordering difference between the client and the server generator, and everyone plays a slightly different dungeon, which quietly destroys the entire premise. Enforcing it meant keeping all shared logic pure, seeding every stage explicitly, and never reaching for wall-clock time or Math.random() inside generation. It is a discipline more than a feature, and it shaped how the whole project is layered.

Procedural floors that can never soft-lock. Randomly scattered walls and pillars are the fastest way to make a room impossible to clear: strand an enemy behind a wall you can neither reach nor shoot past, and the door never opens. The naive guard (only wall a tile whose four orthogonal neighbours are floor) is not enough, because a cross-junction tile is an articulation point whose removal cuts the graph. So every candidate wall is placed tentatively, then a flood fill from the start confirms the reachable-tile count dropped by exactly one, otherwise the placement is reverted. The Binding-of-Isaac rooms use the same trick for their interior obstacle templates, always preserving the centre cross (which also carries every door entry) and reverting any whole template that strands a tile. I verified the guarantee by generating 45,000 floors and confirming zero stranded spawns.

Making a turn-based game feel alive without cheating the player. Enemies patrol in real time and close in if you stand still too long, but combat stays strictly lockstep, and the game is fully safe the moment a menu or a story choice is on screen. Getting the boundary right (idle is dangerous, but deliberation is never punished) took several passes on when real-time bites are allowed to fire.

Balancing an endless mode. The Gauntlet's per-floor power-ups stack, and early on that made deep runs effectively invincible, which flattened the leaderboard into a test of patience rather than skill. The fix was two-sided: a steeper boss curve unique to the Gauntlet (quadratic HP growth plus attack that scales with depth), and a hard cap on stacked lifesteal so healing can never outrun a deep boss's bite.

Everything from code. No art and no audio meant hand-building a pixel-art style, a tinting scheme so one grayscale tile set could paint eight biomes, and a procedural chiptune score, including the cinematic trailer. Tuning synthesised music by ear, with no samples to fall back on, was its own long loop.

Sharing you cannot fake. Letting players post as themselves means the server must treat every incoming run as hostile, rebuild the shareable artifact from trusted facts, and still degrade gracefully across subreddits with different permission grants.

Accomplishments that we're proud of

A complete action rogue-like, with leaderboards, streaks, sharing, and recurring daily content, that fits inside one Reddit post and generates its entire world, art, and music from a single date.

The shared-seed presence. Gravestones on the exact floor where a real player fell, and Death Cards that read as genuine brags because everyone crawled the same crypt. This is the part no standalone daily can copy.

Generation I can trust. Soft-lock-proof floors proven at scale, and a daily that is identical for every player right down to the feed preview, with no server round trip needed to agree on the layout.

A game with zero asset files. Art, sound, music, and even the trailer are all synthesised at runtime.

What we learned

Determinism is a design constraint, not just a technical one. Committing to it early, and enforcing it across client, server, and feed, made the product tighter than it would have been if I had tried to sync state instead.

Devvit Web is a real web stack (Phaser scenes plus Hono routes plus Redis), not the old blocks model. Treating it like the web platform it is, rather than fighting it, is what let a full game fit in a post.

"Everything from code" started as a limitation and became the identity. It forced a coherent look and sound, kept the bundle tiny, and dodged the asset-CSP problem entirely.

What's next for Chrono-Crypt

Full mobile support. The game runs on phones today, but the touch controls and the responsive layout are still being tuned. Making mobile a first-class way to play is the top near-term priority, since most of Reddit is on a phone.

A UI pass. Cleaner menus, HUD, and dialogs with a look that belongs to the pixel-art world, instead of leaning on default dialog widgets. Better readability on small screens feeds straight back into the mobile work.

Deeper boss and enemy AI. Give each boss a readable pattern (telegraphed charges, summons on a cadence, a window where it is actually vulnerable) and smarter minions, so floor 10 fights differently from floor 5 rather than just hitting harder.

Unique and rare encounters. Hand-crafted one-off rooms, a hidden room per daily seed with a finder's reward, and rare events that break the crypt's rhythm and give the community something to hunt for and talk about.

Puzzle rooms. No-combat rooms built on the same deterministic seed, so everyone solves the identical puzzle and can compare how they cracked it. Forfeit one if you get stuck and move on, so a puzzle is never a dead end. A change of pace between fights, and another thing the shared crypt makes comparable.

Ghost races. I already record every death, and the seed makes runs reproducible, so the natural next step is a translucent replay of the current number one that you race on the exact crypt you are playing. No other daily can offer that.

World-mood elections. Turn the story-room morality system from a small stat tweak into weekly community votes cast through play: which boss reigns next week, which biome opens, whether a sanctum or a black market spawns. Winners wear faction flair, and the most innovative system becomes the most discussed one.

Starting classes. Warden, Ranger, and Penitent give the same daily seed three different angles, tripling the comparison ("today's map is a Ranger map") at low cost.

Seasons, mastery, and cosmetic-only economy. Six-to-eight week seasons with resetting boards and a shared narrative that advances on aggregate community behaviour, mastery tracks that award Death Card frames and death effects, and optional cosmetic purchases. Power is never for sale, because the leaderboard's integrity is the product.

Achievements Unlockables with grand (personal) achievement points, special cosmetics, and special user flairs.

Built With

Share this project:

Updates

Submission history