VaultBreakers

A daily shared vault heist that lives inside a Reddit post where everyone breaks into the same vault, and the comment section is the multiplayer.

Inspiration

Two things collided. First, the r/Wordle insight: a game gets dramatically more social when everyone faces the exact same challenge on the same day the fun isn't just playing, it's comparing notes afterward. Second, tabletop push-your-luck games like Diamant/Incan Gold, where the entire design is one delicious question: bank what you have, or go one room deeper?

Reddit's Devvit platform felt like the perfect home, because Reddit already has the missing multiplayer layer built in: the comment thread. I didn't need lobbies, matchmaking, or real-time sync. I needed a 60–90 second run that ends with something worth posting.

What it does

Every day at midnight UTC, the app publishes a new vault post. Each player gets one run per day:

  1. Draft 1 of 3 ability cards (Lucky Boots, Treasure Hunter, Second Sight).
  2. Enter a eight-room vault with the same layout for every player that day and mixing it with timed traps (3-second decisions), riddle-style choices, and chests.
  3. At chests, choose: open it for more gold, bank your haul at a checkpoint, or retreat safely.
  4. Escape with everything, retreat with what you banked, or die and lose half your unbanked gold.
  5. Your result posts as a native Reddit comment (opt-in by default, moderator-configurable) that comment is the leaderboard entry, the brag, and the bait for the next player.

How I built it

Stack: TypeScript end-to-end · Devvit Web · Phaser 4 for the animated stage · Hono on Devvit's serverless runtime · Redis for persistence · Vite. Shared request/response types in src/shared keep client and server honest.

The daily vault is pure determinism. The date string is hashed with FNV-1a into a mulberry32 PRNG, which Fisher–Yates-shuffles the room library. Same seed, same vault, for everyone, with zero stored content. One design constraint made this fun: traps must never appear back-to-back. For $k = 3$ traps in $n = 6$ ordered rooms, the number of non-adjacent placements is

$$\binom{n-k+1}{k} = \binom{4}{3} = 4$$

so the generator picks one of exactly those four legal layouts, then deals shuffled trap and calm rooms into it.

The economy is tuned around one inequality. Suppose you carry $u$ unbanked gold into a room you'll survive with probability $p$, where surviving pays $r$ and failing on your last heart leaves you $\lfloor u/2 \rfloor$. Pushing on beats banking when

$$p(u + r) + (1-p)\frac{u}{2} > u \iff p > \frac{u}{u + 2r}$$

Room rewards escalate (35 → 120 gold) specifically so this threshold hovers near a coin flip late in the vault the "one more room?" decision should always hurt a little.

The server is the only authority. The client never learns which choice is safe; it only sends intents (choose, open, bank, retreat, timeout) and receives a view model with the answer stripped out unless the Second Sight ability explicitly reveals it.

Challenges I ran into

  • Trap timers vs. untrusted clocks. A 3-second timer enforced by the server breaks the moment a phone's clock is skewed. Fix: every response carries serverNow, and the client reconstructs a local deadline as $d_{local} = t_{client} + \max(0,\, d_{server} - t_{serverNow})$, while the server independently rejects late answers with a small grace window. Clock skew became irrelevant.
  • Idempotent daily posts on serverless. The cron trigger, the install trigger, and a moderator menu action can all try to create "today's post" concurrently. A Redis SET NX lock with a TTL, plus careful cleanup on failure, guarantees exactly one post per day no matter who fires first.
  • Live data migration mid-project. Splitting a single loot field into banked/unbanked/lost piles would have corrupted existing runs, so every read passes through a migrateRun shim that upgrades old records on the fly.
  • Juice with zero art assets. Every texture sparks, coins, dust motes is generated at runtime with Phaser's Graphics.generateTexture. Torch flames, gold rain, camera shake, and the depleting trap ring are all procedural, and every one of them is skipped under prefers-reduced-motion.
  • Phaser inside a Reddit webview. The embedded viewport resizes unpredictably, so the scene runs in RESIZE scale mode with a debounced restart, rebuilding the stage from a serialized state machine so nothing visual is lost.

What I learned

  • Determinism is content compression. One hashed date string is the day's level no level storage, no sync protocol, and the shared-challenge social hook comes free.
  • Server-authoritative design matters even for tiny games. The moment results become public comments, someone will open DevTools.
  • Design for the comment, not just the run. Framing the result as a story ("Fell in Room 4, lost 85 unbanked gold") shaped the whole loot system banked vs. at-risk gold exists because it makes better sentences.
  • Platform fluency pays off: Devvit's scheduler, settings, menu actions, Redis, and runAs: USER comment scopes each replaced a subsystem I'd otherwise have built by hand.

What's next

The GDD already goes further than this slice: daily leaderboards, streaks, Snoovatar-based characters so your actual Reddit avatar runs the vault, a larger trap/ability library, and an earned second-attempt economy.`

Built With

Share this project:

Updates

Submission history