Alexei Rojas Quiroga
← All projects

Personal project

The Gauntlet

Dark-fantasy text RPG where a deterministic engine rules and an LLM narrates

Status
In progress
Role
Solo: game design, engine, AI integration, frontend, tooling and deployment
GitHub

Architecture

  • Pure engine, LLM only narrates
  • 10 authored, validated chapters
  • Local fallback narrator
  • Headless playthrough tests
  • Optional multiplayer

The player acts, the engine resolves it against chapter data, emits events and saves; the narrator turns those facts into text through the server route, or locally if the LLM fails.

Architecture · 9 nodes · 2 flows
WHAT I BUILTReact 19 · Nex…Game interfaceTerminal, combat,character viewsTypeScriptGame engineDice, combat, intent,puzzlesIn-processDomain event busResolved facts for thenarratorTypeScriptNarrator clientPrompt builder, lengthbudgetNext.js routeNarration proxyServer-side LLM callNaN Builders A…LLM providerNarration and chaptergenerationTyped data + Z…Chapter and storydataStory graph, monsters,10 chapterslocalStorageSave systemSaves, checkpoints,migrationTypeScriptLocal narratorTemplate narration whenthe LLM fails1234567
  • Service / compute
  • Data store
  • Queue / messaging
  • AI
  • Client
  • Synchronous
  • Async / loop
Scroll sideways to see the full diagram

How it flows, step by step

Click a step to jump to it. Click a component for details.

What it does

A browser-based, bilingual dark-fantasy RPG with character creation, turn-based D20 combat, puzzles, inventory and a multi-chapter campaign. The game engine owns every rule and outcome, while an LLM turns resolved events into narration and generates new chapters that must pass schema and playability validation before they can be played.

The problem

LLM-driven games drift: models forget the campaign, invent outcomes and break rules. The goal was an AI Dungeon Master that is expressive but can never change authoritative game state.

What I built

  • Strict separation between a pure game engine (combat, dice, intent, puzzles, campaign) and an LLM narration layer that never mutates state.
  • Chapters are standardized, validated data, with ten authored chapters and a story graph of choices and conditions instead of hardcoded flow.
  • Headless playthrough harness drives the engine across every archetype and origin, solving and abandoning puzzles, and asserts the player is never stranded.
  • Save system with legacy migration and campaign-progress folding, so persistence never depends on the model remembering the story.
  • Domain event bus feeds the narrator; narration length is a single budget shared by prompt and client so output is written within limits, not truncated.
  • LLM chapter generation: a server route asks the model for an outline and a full bilingual chapter, then validates it with a Zod schema and a graph validator, repairing it for up to three attempts.
  • Validator rejects any chapter with a reachable state that can no longer finish (inescapable loops), a defect found when running real generated chapters; transport failures retry separately from model repair attempts.
  • Optional WebSocket server for multiplayer rooms.

Key decisions and why

01

The engine is authoritative, the LLM only narrates

Dice, combat and story state are deterministic code. The model receives resolved events and returns prose, so outcomes are reproducible and testable and the AI cannot cheat or contradict the rules.

02

Chapters as data, not code

Moving chapters into a standard validated format removes hardcoded ids from the engine and makes new chapters, including LLM-generated ones, safe to load and verifiable offline.

03

LLM calls go through a server route

The browser calls a Next.js API route which holds the API key and talks to the provider, keeping the secret server-side and the client simple.

04

Puzzles are always solvable offline

Puzzles never call the LLM and share one shape across engine, validator and chapter contract, so progression can't stall on a bad generation.

05

Generated chapters pass the same gate as authored ones

The server never returns a chapter that has not passed both the schema and the full reachability validator, so a bad generation returns an error (422) instead of a broken campaign.

Tech stack

Languages
TypeScript
Frontend
Next.js 16React 19Tailwind CSS 4
Backend
Node.js route handlers
Messaging
WebSocket (ws)
Other
Zod
AI
NaN Builders LLM API
DevOps
Docker (multi-stage, pnpm)
Testing
tsx validation and playthrough scripts