rpg-combat-pi-01/README.md

4.4 KiB

RPG Combat

A challenge set by Emily Bache. The kata was invented by Daniel Ojeda Loisel and the description is adapted from Steve Smith's version.

What This Is

RPG Combat is a small rules engine for a tabletop-style RPG. Characters fight, level up, join factions, and wield magical objects — all governed by a precise set of business rules. The challenge is to implement those rules correctly, and this project takes a different path than most: instead of writing code first and tests later, we start with formal specifications and let properties drive every line of implementation.

Original Source

The user stories and rules come from this kata description, originally created by Daniel Ojeda Loisel and adapted from Steve Smith's version.

user-stories.md          ← the requirements (what the system should do)
.specs/                  ← Allium formal specifications (the formal model)
src/                     ← TypeScript implementation (ADTs, value objects, immutability)
*.spec.ts                ← fast-check property tests (executable verification)

How We Work

The workflow follows three intertwined practices:

1. Spec-First with Allium

Before writing any code, requirements are formalized into Allium — a logic-based specification language. Each user story becomes a .allium file with invariants (always-true properties) and rules (state transitions). This is the source of truth.

2. Property-Based Testing with fast-check

Allium invariants are translated into fast-check properties. Instead of hand-crafting individual test cases, we define properties that must hold for thousands of random inputs:

// "Health is never negative" — verified across 1000 random damage values
fc.property(fc.integer({ min: 0, max: 10000 }), (damage) => {
  const c = new Character({ name: 'goblin', health: 1000 });
  c.takeDamage(damage);
  return c.health >= 0;
});

3. "I Can't Believe It's Not Haskell"

The TypeScript implementation embraces functional patterns:

  • ADTs (algebraic data types) via discriminated unions for states
  • Value objects (Health, Level, Status) with invariants enforced at construction
  • Immutability — no mutation, pure functions, new instances returned
  • YAGNI discipline — write only what a failing property demands

What We've Done

All five user stories are implemented and verified:

Story Topic Status
1 Character Creation & Damage Done
2 Levels Done
3 Factions Done
4 Magical Objects Done
5 Changing Level Done

70 tests passing across 6 spec files.

The Journey: Skills & Tools

The real value of this project isn't the code — it's the process. Here's what was built along the way:

Built-in Allium Skills

Six Allium skills guide the workflow from requirements to verified code:

  • /skill:elicit — explore and clarify requirements with stakeholders
  • /skill:distill — extract specifications from existing code
  • /skill:propagate — generate test obligations from specs
  • /skill:tend — evolve specs as understanding deepens
  • /skill:weed — check spec-code alignment
  • /skill:user-story-conversation — Card, Conversation, Confirmation workflow with Example Mapping, Allium specs, and fast-check properties

Custom Extensions

Two custom extensions were developed for this project:

  • clear-export — exports the current pi session to an HTML transcript in transcripts/ and starts a fresh session. This creates a permanent record of each decision, iteration, and insight.
  • problem-breakdown — breaks problems into small, independently executable steps using the yx CLI. Each step becomes a "yak" with full execution context, enabling parallel or sequential execution by any agent.

The Transcript Archive

Every session is exported as an HTML transcript in the transcripts/ directory — over 20 sessions documenting the full journey from first requirements review through horizontal refactoring. These are the project's most valuable artifacts.

Build & Test

npm install
npm test          # 70 properties across 6 spec files
npm run lint:fix
npm run format:fix
npm run typecheck
npm run checks    # pre-commit gate: format + lint + typecheck + test