Sublurk is real tabletop D&D running inside a Reddit post. You can play alone or with your community. Choose one of 3 proposed actions, or type what your character does in plain words, a d20 decides whether it works, and the dungeon narrates back. There are no action buttons.

Inspiration

The idea mostly came from an observation. Reddit already has a decision-making machine built into every post: comments propose, upvotes decide. That is a D&D party arguing over whether to open the door. I did not have to invent the mechanic. I just had to point it at a d20.

What it does

Community. The whole sub steers one crawler by comment vote. Turns resolve on a timer whether you are watching or not, so the run moves while you sleep and there is always a new floor to come back to.

Solo. Fall in alone and descend the Hollow King's dungeon, a hand-authored world with a fixed final boss. This is the tutorial and the personal challenge.

There is no action list. You can type "check the corpse's teeth for gold" or "ask the wraith who it used to be" or "throw my torch at the ceiling and hope something moves," and the Warden takes it seriously, in character, and folds it into the room it already described.

Every action resolves as a d20 ability check against the room's difficulty. Six abilities, real modifiers, advantage and disadvantage that cancel out. Classes have signature abilities that fire mid-narration: a Fighter survives one killing blow per run at 1 HP, a Rogue rerolls one failed action per floor, a Wizard curses a foe to ease a strike.

The leaderboard. Every solo player holds exactly one place: their deepest descent, ever. A new run only takes your spot if it actually beat your old one, so the board is a hall of personal bests rather than a feed of recent attempts. Ranking is depth first, and at equal depth the player who got there in fewer rolls placed better, so efficiency breaks ties. Each row shows the crawler you played, and if you actually killed the Hollow King, a crown sits next to your name. Most people never get one, and that is the point.

The hook

Your whole subreddit shares one character, one HP bar, and one way out: down. Someone comments "search the altar." Someone else comments "burn it instead." The sub upvotes. The top comment becomes what the character actually does, the d20 decides whether it works, and everyone lives with the result. There is no undo. When the character dies, they stay dead, and the next one starts at the top with nothing but the story of how the last one failed. The comment section is the game's input device.

This is a dungeon crawler, not a story generator

The distinction that matters: the AI never decides anything.

The server rolls the dice first. It resolves combat, damage, healing, and death deterministically. Only then does it tell the model what happened and ask it to narrate that outcome and nothing else. Every field the model returns is coerced to a strict schema and clamped before it touches game state.

So the Warden, the voice of the dungeon, can describe your death gorgeously. It cannot cause it, prevent it, hand you a free escape, or heal you 40 HP because you asked nicely. It is an actor reading the dice's script.

That constraint is also what makes the narration good. The model is never asked to be smart, or fair, or to reason about rules. It is asked to take an arbitrary sentence and return grounded, specific, atmospheric prose that stays consistent with a world it was handed. That is the job it does best, and the more tightly I fenced it in, the better it got.

It is why the game has stakes. A collaborative story where the narrator decides outcomes has no failure state. This one has a boss you can lose to, math you can check, and a run history that remembers.

It is also the part that improves without touching the engine. A sharper persona, a tighter rule, a better world bible, and every future turn gets better retroactively. The mechanics are finished and provable. The voice is a dial I can keep turning.

Why it belongs on Reddit specifically

Reddit already had the mechanic I needed. Comments are proposals. Upvotes are a voting system with built-in spam resistance and a sorting algorithm. A post is a persistent shared surface with a notification system attached. Every one of those is load-bearing here, not decoration.

And the dungeon itself is made of the community. When the game is installed in a subreddit, it reads that sub's own public posts and generates a bespoke campaign: its own setting, its own motifs, its own named final boss at the bottom of its own map. r/coffee does not crawl the same dungeon as r/spaceporn. The community is not playing a game about their sub. Their sub is the level.

Why you come back

  • Permadeath with a memory. When a run dies, the run is over for good, but the dungeon keeps a record. The next run opens with the nemesis taunting you about exactly how the last one ended. Loss compounds into lore.
  • The run progresses without you. Turns resolve on a schedule. Whatever the sub decided while you were gone already happened. Coming back means finding out what your community did with the character you share.
  • A ladder you are on. One entry per player, your best descent only, marked with the crawler you played. Beating the Hollow King puts a crown on your name.
  • A campaign with an actual ending. The map has a bottom. There is a specific named thing waiting there. Reaching it is possible and rare.

How I built it

  • Devvit Web. Hono server, React 19 + Vite + Tailwind webview, strict TypeScript throughout.
  • An SRD 5.1 engine, written from scratch. Dice with advantage, a difficulty ladder, six abilities, conditions that drain HP and impose disadvantage, class room affinities, server-authoritative combat with per-foe HP that persists across exchanges.
  • Google Gemini as the Warden. Narrating outcomes and authoring each subreddit's world bible and campaign map. Never rolling, never resolving.
  • Redis for state. A per-subreddit shared run, private per-user solo runs, leaderboards, and a lore log that powers the nemesis.
  • 302 tests on the deterministic core: dice, combat, healing, shrines, resolution, serialization, signatures, and the AI response coercion.
  • Built from scratch during the submission period. Published and approved by the Reddit team as sublurk-app.

Challenges I ran into

These are the ones worth writing down. Every one was invisible from the outside and would have quietly ruined the game for anyone who played it seriously.

A boss that could not be beaten, hidden behind a bug that let you win anyway. Late in testing I had a run where every roll failed and then I abruptly won. Two bugs were cancelling each other out. Difficulty scaled with depth, so the final boss sat at DC 30 while a player's maximum possible total is 23, and a miss dealt zero damage. The fight was mathematically unwinnable. The only reason anyone ever won was a second bug: the safeguard that forces you out of a room after repeated failures was granting a victory at the final boss. Fixing either one alone would have shipped a fake win or an impossible one. The fix was three parts. Failing at the boss is now death, difficulty caps at DC 20, and a non-critical miss grazes so attrition always inches forward. A natural 1 still whiffs completely.

Turns that silently happened while the player was told they had not. When the narrator was unreachable, I showed the player an "out of energy" cooldown and told them nothing had happened. But the server had already rolled the dice, applied the enemy's counterattack, spent the Wizard's once-per-floor curse, and saved all of it. Your HP bar dropped for no stated reason and your signature ability was gone with no story beat explaining where it went. The turn was a lie in one direction and real in the other. Now an unreachable narrator returns the original state untouched, before the roll and before any effect, a true no-op. The community resolver leaves the deadline due so the scheduler retries the same winning comment rather than consuming the sub's vote on a wash.

Two things the client was only pretending to enforce. I audited the whole codebase near the end and found two real holes, both from the same mistake: treating the client as a boundary. The "resolve the turn" and "new run" controls were hidden from non-moderators in the UI, but the endpoints behind them had no server-side check at all, so anyone with devtools could wipe a subreddit's shared run or force a turn before votes settled. Separately, an early direct-action endpoint was still live after I removed its input from the client, which meant one person could run turns on the shared game and burn the party's HP to zero while bypassing the vote entirely. The two mod controls now verify moderator status server-side and return 403. The direct-action endpoint is deleted.

A feature that silently never fired on its main path. The nemesis system, the taunt that remembers exactly how your last run ended, recorded a run's outcome only when a turn was resolved from inside the app. But the primary resolver is a scheduled job that runs every minute, and it called the resolution function directly, skipping the recording. So in real use, on the path that resolves almost every community turn, the nemesis never learned anything. The recording now lives inside the resolver itself, so the scheduler, the mod menu, and the in-app button all feed it.

Trusting the model's account of the dice. The escalation valve that forces a party out of a room after repeated failures counted failures based on the outcome the model reported in its JSON, not the outcome the server actually rolled. A model that drifted, or the authored fallback content used during an outage (which always reports "partial"), could stall the valve indefinitely or reset it on every outage. It now counts the server's own dice result. The whole architecture is built on not trusting the model, and I had left a hole in it in the one place it was least visible.

A game that got quieter the more popular it got. I fetched the post's top 100 comments and then filtered them down to the current turn. On a post with more than 100 lifetime comments, this turn's proposals could fall outside that window and become invisible and unvotable. The failure mode was exactly backwards: the more a community played, the less their votes counted. Now I fetch newest first, so the time filter and the sort agree, and rank by score afterward.

Accomplishments that I'm proud of

The Warden never breaks character, and never breaks the rules. The narration is free-form and the mechanics are airtight, at the same time. That was the hard part and it is the part that works.

The leaderboard ranks on two axes with one number. A Redis sorted set can only rank on a single value, so the score packs both stats into one: depth × 100,000 + (99,999 − rolls). A plain descending sort then orders by depth first and by fewer rolls second, in a single operation, with no post-processing. A companion hash stores the crawler and the win flag per player.

Two genuinely different games share one engine. A private, immediate, single-player descent and an asynchronous, shared, vote-driven campaign run on the same dice, the same combat, and the same permadeath. Neither is a reskin of the other, and neither does the other's work.

A rate limit became a diegetic mechanic. Free-tier quotas meant a fast player could hit a wall and the game would look broken. Instead, the Warden "spends itself" and a regenerating energy bar counts down before you can act again. The failure state is in the fiction rather than in an error toast.

Every promise the character screen makes is one the engine keeps. For a while the class signatures were display strings and nothing more. They are all real now, and each one fires with its own visible line in the narration so you can see it happen rather than infer it from a number.

What I learned

I learned that "it passes the tests" and "it works" are different sentences. Every one of the bugs above was in code that compiled, linted, and had green tests. The suite could prove the dice were fair and the damage math was right. It could not tell me the boss was unreachable, that the nemesis never remembered anything, or that a hidden button is not a locked door. The tests guarded the parts I already understood.

I learned that two bugs can hide each other well enough to look like a working game. The unwinnable boss and the false victory produced, together, something that superficially resembled a hard fight with a rare win. Fixing one would have made the game visibly broken. I only found them because a run felt wrong, not because anything failed.

And I learned the counterintuitive thing about building with a language model: it got better every time I took power away from it. When it could decide outcomes, it was a pushover that handed out free wins. Once it could only describe outcomes the server had already decided, it became genuinely good: menacing, specific, consistent. The interesting work was not prompting. It was drawing the line between what the model authors and what the server owns, and then defending that line in every place I had accidentally left it open.

Honest limitations

  • It runs on Gemini's free tier, because I am a student and cannot pay for a paid one. That means a hard ceiling on how many turns can be narrated in a day, shared across every player. The game degrades gracefully when it runs out, since the Warden "spends itself" and an energy bar counts down, but it is a real constraint and not a design choice. The moment there are enough people playing to justify it, moving to a paid tier is a config change and a spend cap, and the ceiling disappears.
  • The interface was built by an engineer, not a designer. The choices are deliberate: the type is modelled on a stat block, with one family in two cuts and true small caps for names, and red is used structurally rather than decoratively. I stand by them, and I also know the ceiling of what I can do alone. Bringing in people with a real eye is the fastest way to make the game look as considered as it plays.
  • The community lane needs a real community. Every mechanic is built and tested, but a voting game is only as good as the number of people voting.

What's next for Sublurk

  • Hand-authored worlds, with the AI as the finishing pass. Right now the AI generates each subreddit's setting, villain, and campaign map from scratch. It does this well, but "well" and "memorable" are different things. I want to write the villains myself: real antagonists with motives, histories, and a reason to be at the bottom of a specific dungeon. Real maps with authored locations. Real NPCs who help you, lie to you, or want something. Then let the AI do what it is actually best at, which is reskinning that authored skeleton to fit the community it lives in, and narrating it. Human structure, machine surface. The opposite of the current split, and I think a much better game.
  • Customizable characters. Right now you pick one of three crawlers. I want you to build one: choose abilities, pick a signature, name them, and have that character be yours across runs, with the leaderboard remembering who you played as and what they achieved.
  • Parties. You and three friends from your sub start a run together, each controlling your own character, hopping on whenever you have a minute. The engine already supports two run scopes, one shared per subreddit and one private per user, so a party run is a third scope keyed to a group rather than a sub or a person. The turn pipeline, the dice, and the permadeath all stay exactly as they are. This is the feature I am most excited to work on next.
  • Live post previews. The feed card itself shows depth, HP, and the voting timer counting down, so the post becomes a live story beat instead of a static thumbnail.
  • Contributor credit. Track whose comment steered each turn and surface the sub's most influential voices, the people who actually drove the run.
  • Signature abilities as a pressed button rather than an automatic trigger, so they become a decision instead of a bonus.
  • Mod-configurable turn cadence, from minutes to daily, so a sub can set the rhythm that fits it.
  • Cross-subreddit leaderboards. Which community got deepest.

Credits

This work includes material taken from the System Reference Document 5.1 ("SRD 5.1") by Wizards of the Coast LLC and available at https://dnd.wizards.com/resources/systems-reference-document. The SRD 5.1 is licensed under the Creative Commons Attribution 4.0 International License available at https://creativecommons.org/licenses/by/4.0/legalcode.

Built With

Share this project:

Updates

Submission history