[ Case Study ]

Party Wipe

I wanted to play D&D, but never had a committed group. Party Wipe is the combat half — a 20-minute single-player roguelike, no DM required.

In development · Engine and UI complete
  • Next.js
  • React
  • Tailwind CSS
  • TypeScript
Title screen — Cinzel typography over amber-gold brandParty select — class cards, draft modeRoom preview — encounter incoming, enemy lineup, zonesCombat scene — zones, intent broadcast, status visuals

[ The Problem ]

D&D has a gatekeeping problem. A committed group, a willing DM, four-plus-hour sessions, paper bookkeeping, a rules tome longer than most novels. Two audiences bounce off it: gamers who'd be receptive to tabletop but won't commit, and non-gamers who'd try a shortplay if the rules barrier disappeared.

Most of that friction lives in combat. Story tracking is solvable — campaign wiki, DM-managed objective tree, prepared encounters. Combat is the harder problem: conditions, action economy, dice math, resource bookkeeping, damage-type interactions. New players bounce off the rules density before they ever feel the strategic loop underneath.

I wanted to find out what "consumable D&D combat" could look like, with no good way to learn it from the table. So I built Party Wipe — a single-player roguelike that runs the combat layer end-to-end without a DM, on a heavily curated SRD subset. Longer-term, this becomes the combat engine inside a DM/player dashboard that lowers the barrier for casual table play.

[ Architecture ]

The game is five things working together.

Party Wipe is built on Loom, the same design-system pipeline behind Paperboy — one questionnaire in, two visually distinct products out.

[ Decisions ]

Combat first, story later. Most D&D digital adaptations render the whole experience: narrative, exploration, character relationships, combat. Party Wipe deliberately doesn't. Story tracking is the solved-ish half (wikis, objective trees, prepared encounters); combat is where the friction lives. Building only the combat half means the engine gets sharpened on the actual hard problem before it has to plug into a larger system.

Zone abstraction over grid. Most digital D&D either keeps the grid (BG3, Solasta) or abstracts to text-only (most CRPG-likes). Three zones is the middle abstraction that preserves positioning as a tactical choice without making turns expensive. Newcomers read the battlefield instantly.

Qualifier as field, not event. CRIT, VULNERABLE, and RESISTED used to fire as separate floating popups offset from the damage number. Folded them into a qualifier field on the damage event itself, rendered as a small uppercase ribbon anchored above the number-in-glyph. Three events became one. The visual hierarchy is identical every time, so the pattern becomes recognizable instead of soup.

Visual death decoupled from data death. Combat resolvers flip isAlive: false immediately, but ZoneToken holds the visual alive for 1200ms after a kill event — long enough for the damage flourish to play through before the card grayscales. Five kill paths (player melee, player spell, enemy melee, enemy save-AoE, DoT) all emit the same event and get the same hold.

One source of truth per visual concern. Damage, condition, intent, and class colors each live as a TS registry reading from CSS custom properties in game-tokens.css. No component owns its own color map. Adding a new damage family is one file. Theming a new screen is reading existing tokens. The architecture survives expansion when the trimmed content grows back to full breadth.

[ By the numbers ]

~20mRun lengthconsumable session
18Monstersstrategy loops
10Feedback familiesthemed flourishes
0Menusphase is the screen

[ Under the Hood ]

The roster is the load-bearing artifact — every monster annotated with the strategy loop that earns it a slot.

> src/data/v1-roster.ts
/**
 * V1 Roster — Curated game content
 *
 * Every entry here is distinct, functional, and intentional.
 * Generators and UI filter against these lists.
 * Nothing outside the roster appears in gameplay.
 *
 * Philosophy: FF GBA / BG1 / Fallout 1 — small scope, everything works.
 */

// ─── Monsters (18 curated from 304) ────────────────────────────
export const V1_MONSTERS = new Set([
  // ─ Floor 1 — CR 0.125–0.25 ─
  'giant-rat',     // 0.125 — mob: vanilla cleanup fodder
  'goblin',        // 0.25  — mob: vanilla flexible attacker
  'skeleton',      // 0.25  — bludgeoning-vulnerable + poison-immune (damage-type teacher)
  'wolf',          // 0.25  — Bite inflicts prone (STR save)

  // ─ Floor 2 — CR 0.5–1 ─
  'shadow',        // 0.5   — resists nonmagical physical, vulnerable to radiant
  'ghoul',         // 1     — Claws inflict paralyzed (CON save)
  'giant-spider',  // 1     — Bite inflicts poisoned (CON save); the poison creature
  // ... 11 more, each carrying one strategy loop the engine actually wires
]);
// The curated 18 monsters / 23 spells / 14 weapons / 6 consumables — each comment explains why the entry earned its slot

[ More Case Studies ]

JAMIE

A persistent AI development partner — part JARVIS, part operating system for how I work.

[ read case study ]

Loom

I kept burning out building UI foundations, so I built a pipeline that generates them.

[ read case study ]

Paperboy

A daily news dashboard I built so I’d stop opening six apps every morning.

[ read case study ]