reikhelm/deepfall/DESIGN.md
Parley Hatch 277c158378 feat(deepfall): board-game design + balance-by-bot simulator
DEEPFALL: a tabletop game mashing up 9 loved games (SmashUp, Dragon
Rampage, Blood Rage, Catan, EverQuest, Daggerfall, Eye of the Beholder,
Quarriors, SmallWorld). Core fusion = SmallWorld decline + Blood Rage
glorious death -> three exits (Cash Out / Press On / Worthy End) under a
rising Maw that collapses a reikhelm dungeon floor-by-floor.

- deepfall-core: pure deterministic engine (vendored ChaCha8, 11 tests)
- deepfall-sim: balance-by-bot sweeps -> CSV/JSON
- analysis/analyze.py: stdlib ASCII heatmap (no deps)
- DESIGN.md (rules + post-critique revisions), FINDINGS.md (9-iteration
  balance journey), README.md
- reikhelm-core/examples/sample_dungeon.rs: dumps a real generated board

Method: adversarial design critics -> build -> simulate -> tune. 9
iterations compressed combo spread 77.8 -> 32.5 pts (all 36 combos
viable). Fixed deep-content lockout (value rises with the Maw), the
multi-collapse feel-bad (1 collapse/round cap; feel-bad now 0.00/game),
and dead combos. Open finding: Cash-Out and Worthy-End are substitutes
for a glory-maximizing bot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 22:49:33 -06:00

374 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# DEEPFALL — Design & Simulation Spec
This is two documents in one:
- **Part A — The Game.** A playable tabletop design (the rulebook in prose).
- **Part B — The Simulation.** The faithful abstraction the Rust engine implements,
so the balance numbers mean something. Where the sim simplifies the tabletop rules,
it says so explicitly.
The vocabulary (room **themes**, depth, connectivity, doors) is borrowed straight
from `reikhelm-core` so a printed board is a real generated dungeon.
---
# Part A — The Game
## 1. The fantasy & the arc
Reikhelm is a vertical megadungeon of stacked **Floors**. Floor 1 is the
**Threshold** (the surface mouth, safe, poor). The deeper the floor, the richer and
deadlier it is. Beneath the deepest floor waits **the Maw** — a rising hunger that
devours the hold one floor at a time, **from the bottom up**. The richest plunder is
therefore the most doomed: a deep Vault is worth a fortune and will be eaten *first*.
Rival **delve-companies** (players) descend to plunder before it's all gone. You don't
win by surviving — you win by **Glory**, and the surest way to bank a fortune is to
spend a party deliberately: send them quietly *to ground* with their loot, or hurl
them into a **Worthy End** at the bottom and let the Maw take them gloriously.
The game lasts until the Maw eats Floor 1. Most Glory wins. Nobody is ever eliminated.
## 2. Your delve-company: Lineage × Calling
At the start, and **every time you bring in a fresh party**, you draft two halves that
smash together (Smash Up / Small World / EverQuest / Daggerfall). Each half grants an
**advantage** and a **cost** — there are no free builds (Daggerfall's custom-class
trade-off).
### Lineages (the "race" — sets your starting **dice bag** + a passive)
| Lineage | Advantage | Cost (disadvantage) | Bag leans |
|---|---|---|---|
| **Dvergar** | +1 Steel resolved each turn (sturdy) | 1 Move (slow) | Warrior |
| **Wisp** | +1 die drawn each hand (nimble) | Setbacks hit double (fragile) | Scholar |
| **Revenant** | Ignore the first Dread each roll (fearless dead) | Faith 1 (no living soul) | Balanced, dread-proof |
| **Gnoll** | +Loot from combat rooms (Den/Forge) (scavenger) | Cunning 1 (clumsy at locks) | Warrior/Rogue |
| **Sylphid** | +1 Move (fleet) | Steel 1 (frail) | Scholar/Rogue |
| **Golem-born** | Setback-resistant (rarely loses dice) | Cannot recruit Relic dice (rigid) | Big, balanced, slow to grow |
### Callings (the "class/faction" — special ability + recruit pool + scoring lean)
| Calling | Special | Recruits | Scoring lean |
|---|---|---|---|
| **Reaver** | Pillage: extra loot from every room you claim | Warrior dice (cheap) | Loot |
| **Lorekeeper** | Open Vaults **solo** (no trinity needed); +Cunning at locks | Scholar dice | Deep vaults |
| **Wardancer** | Worthy-End payout **+50%**; cheap Moves | Rogue dice | Glorious death |
| **Hierophant** | Cancel extra Dread; heal Setbacks; cheap denizen rep | Priest dice | Longevity (persist) |
| **Warden** | Your control markers count **double** for majority | Champion dice | Area control |
| **Prospector** | Cheaper recruits, bigger hand, can find **Relic** dice | Relic dice | Engine / snowball |
6 × 6 = **36 companies.** Natural synergies emerge (Dvergar Reaver = smash-and-grab;
Wisp Lorekeeper = vault cracker; Sylphid Wardancer = fast martyr; Golem-born Warden =
the immovable wall) — the simulator's job is to confirm none of them is *solved*.
## 3. The dice bag (Quarriors) & the six faces
Your bag is a pile of six-sided **delve dice**. Each face shows one symbol:
| Symbol | Meaning |
|---|---|
| ⚔ **Steel** | Combat power — clear a room's threat |
| 🜏 **Cunning** | Wits — open mechanisms / locks; buy extra Moves |
| ✚ **Faith** | Spirit — cancel Dread; heal Setbacks; appease denizens |
| 💰 **Greed** | Loot — turns into banked Glory |
| ✦ **Mettle** | Wild currency — recruit new dice; pay for actions |
| ☠ **Dread** | Peril — accumulates; too much triggers a **Setback** |
Different **die kinds** weight these faces differently. Starting bags are mostly
*Recruit* dice (balanced, one of each). You grow your bag in play by **recruiting**
stronger kinds (Warrior/Scholar/Priest/Rogue/Champion/Relic) from the dungeon's offer.
### A turn
1. **Draw** a hand from your bag (default 4 dice; modified by Lineage/Calling).
2. **Roll** them.
3. **Resolve Dread** — cancel Dread with Faith (and Lineage dread-resist). If Dread
still standing ≥ the **Dread threshold**, suffer a **Setback** (lose a die to your
bag's wound slot, or lose unbanked loot).
4. **Act** with your symbols — Move between floors, clear & claim a room, take loot,
recruit a die.
5. **Press On?** (push-your-luck) — you may draw & roll *another* hand for more symbols
this turn. Every press nudges the Maw and stacks more Dread risk. Stop whenever you
like; greed is punished by the dice and by the clock.
6. **Exit decision** — see §6.
## 4. The dungeon: rooms, themes, mechanisms (EotB / Catan / reikhelm)
Each Floor holds several **rooms**, each with a **theme** from reikhelm's palette. A
room's base Glory and its threat/lock scale with its theme and its **depth**:
| Theme | Base value | Threat (Steel) | Lock (Cunning) | Note |
|---|---|---|---|---|
| Threshold | 1 | 0 | 0 | safe mouth |
| Stone | 2 | 1 | 0 | plain |
| Cistern | 3 | 1 | 1 | flooded |
| Den | 3 | 3 | 0 | monster lair (Gnoll loot bonus) |
| Crypt | 3 | 2 | 1 | undead |
| Hall | 4 | 1 | 1 | pillared |
| Library | 4 | 1 | 2 | Cunning-gated, loot-rich |
| Forge | 5 | 4 | 1 | lava, produces Steel |
| Vault | 6 | 2 | 3 | **needs the trinity** (Steel+Cunning+Faith present) unless Lorekeeper |
| Throne | 7 | 5 | 2 | boss; deepest |
A room's collapse Glory = `base_value × depth`. A Vault on Floor 6 is worth `6 × 6 =
36` to whoever holds the majority when Floor 6 falls. **Holding** a room also makes it
**produce** each turn you roll its matching symbol (Catan): a held Forge throws extra
Steel, a Library extra Cunning, a Vault extra Greed — your engine grows on the ground
you keep, right up until the Maw takes it.
**Claiming** a room: spend Steel ≥ threat (fight through) and Cunning ≥ lock (crack the
mechanism). Vaults additionally require **trinity presence** (you produced at least one
Steel *and* one Cunning *and* one Faith this turn) — the EverQuest holy-trinity made
literal — unless your Calling is **Lorekeeper**. Claiming drops a **control marker**.
## 5. The Maw: the doom clock (Blood Rage / Dragon Rampage)
The Maw begins below the deepest floor and **rises**. Each round it advances; its speed
**ramps up** over the game and is **pushed faster by the total Dread** everyone rolled
that round (your collective greed feeds it). When the Maw reaches a floor:
1. **The floor collapses and scores.** For each room, the **majority** marker-holder
takes its Glory (`base × depth`). Ties split, rounding down. A **Warden's** markers
count double here; a party that went **to ground** leaves silent **Fallen** markers
that still count.
2. **Anything still on that floor is Taken by the Maw** (see §6).
Because collapse runs **bottom-up**, the deep treasure has the *shortest* window. The
game ends when Floor 1 collapses.
## 6. The three exits (the heart of the game)
Every party, every turn, faces the same triangle. This is the design's core, fusing
Small World's Decline with Blood Rage's glorious death:
- **Cash Out / "Go to Ground"** *(Small World Decline).* End this party's adventuring.
**Bank** their unbanked loot + a **depth dividend** (deepest floor reached × factor).
Their control markers become permanent **Fallen** markers — still counted at every
future collapse, but they can't move or act. Then draft a **fresh** Lineage×Calling
and re-enter at the Threshold. Safe, repeatable, keeps your territory working for you
from beyond the grave.
- **Press On / Persist** *(Catan/Quarriors engine).* Keep the party active: keep
recruiting, keep holding rooms, keep producing. More upside, but the Maw is climbing
and a party caught flat-footed loses *everything* unbanked **and** its territory.
- **Worthy End** *(Blood Rage Valhalla).* Declare a **Last Stand** (free if you hold a
Throne or Vault, or via Wardancer). Now, when the Maw **Takes** your active party, you
score a huge **Worthy End** bonus = `deepest_floor × worthy_factor` + held room
values (×1.5 for Wardancer). This is the single biggest payout available — and it's
all-or-nothing: you lose the party and its unbanked loot. Sometimes you *want* the
Maw to eat you, as deep as possible, in a blaze.
Getting Taken **without** a Last Stand is the one true feel-bad in the game (lose loot
+ territory for nothing). The simulator measures how often bots fall into it — a
healthy design keeps that rare, because the exits above are always offered.
## 7. Denizen reputation (Daggerfall / EverQuest — light)
A few **denizen** factions live in the deep (the Deep Cult, the Gnoll Warrens, the
Revenant Court). Clearing their themed rooms angers them (reputation down → they raise
the threat of their rooms against you); making **offerings** (spend Greed/Faith) raises
reputation → unlocks premium recruit dice and safer passage. One short track per
denizen, two or three thresholds. *(Kept deliberately light; the sim models it as a
small threat modifier so it doesn't dominate the balance picture.)*
## 8. Why this fixes the originals' worst parts
- **No runaway leader.** A trailing player dives recklessly, takes a Worthy End or a
deep Cash-Out for a glory spike, and resets — catch-up is *built into* the core loop,
not bolted on. Leaders who over-persist feed the Maw and lose territory.
- **No elimination, no downtime.** A dead party is glory and silent territory, not a
removed player. Turns are short; you're always choosing.
- **Variance with a floor.** The dice can betray you, but Faith mitigates Dread and
Cash-Out lets you lock in value before luck turns — Quarriors' whiff is defanged.
- **Conflict without spite.** You fight for *majorities at collapse*, never to destroy
an opponent's hand. The shared "robber" is the Maw, and it is perfectly fair.
---
# Part B — Simulation model
The engine in `deepfall-core` is a **faithful abstraction** of Part A, not a literal
table simulator. It keeps everything that drives the six balance questions (the dice
faces, the bag-building, the three exits, the bottom-up Maw, the area-control scoring,
the Lineage×Calling asymmetry) and abstracts the fiddly bits (exact board geometry,
turn-by-turn movement pathing, denizen micro-management) into clean numeric rules. Each
abstraction is called out below.
## B1. State
- **Dungeon**: `floors[1..=D]`, default `D = 6`. Floor `f` has `R` rooms (default 4),
drawn from the theme table weighted toward richer themes at greater depth. Each
`Room` has `theme, base_value, threat, lock, produces(symbol), markers: Vec<Marker>`.
*(Abstraction: rooms are an unordered set per floor; we don't model intra-floor
pathing or the corridor graph — only "which floor are you on" matters for the Maw.)*
- **Maw**: `accum: f64`, `frontier: u32` = deepest uncollapsed floor (starts at `D`).
- **Players**: `glory`, and an optional active `Party`.
- **Party**: `lineage, calling, bag: Vec<DieKind>, floor (starts at 1), unbanked_loot,
deepest_reached, last_stand: bool, held: Vec<RoomRef>`.
- **Marker**: `{ owner, strength, fallen }`. Active strength = 1 (×2 for Warden);
Fallen strength = 1.
## B2. Round structure
Each round, in player id order, every player takes a **turn** (B3). After all turns,
the **Maw advances** (B4) and any collapses resolve (scoring + Takes). Repeat until the
frontier passes Floor 1. Then **final scoring** (B5).
## B3. A turn (the engine's core resolution)
1. **Decide intent** from the controller (B6): a *target floor* and a *risk appetite*.
2. **Move**: `moves = 1 + lineage_move + calling_move`; optionally spend Cunning/Mettle
for extra moves if the bot wants to dive. Step `floor` toward the target (clamped to
`[1, frontier]` — you cannot stand on an already-eaten floor). Update
`deepest_reached`.
3. **Roll** the hand: draw `hand_size` dice (`+lineage/calling`), roll each to a face,
summing the six symbol totals. The controller may **Press On** (B6) to draw & roll
additional hands, each appending to the totals and adding `press_dread_push` to the
round's Maw pressure.
4. **Dread**: `dread -= faith_cancel` (Faith spent to cancel, plus Hierophant bonus
and Revenant's "ignore first"). If remaining `dread ≥ dread_threshold`, a **Setback**:
lose one die from the bag (Golem-born resists; Wisp loses two) **or**, if the bag is
minimal, lose `setback_loot` unbanked loot. Count it.
5. **Claim**: among rooms on the current floor not already held by this party, pick the
highest-value one the party can afford this turn (Steel ≥ threat, Cunning ≥ lock,
trinity-or-Lorekeeper for Vault). Place/strengthen a marker; add to `held`.
*(Abstraction: one claim attempt per turn; "contest" = simply adding your own marker,
majority resolved at collapse.)*
6. **Loot**: `unbanked_loot += greed × loot_per_greed × depth_factor(floor)
+ production(held) + reaver/gnoll bonuses`.
7. **Recruit**: if `mettle ≥ recruit_cost` and the controller wants to grow, add a die
of the Calling's recruit kind to the bag (Prospector discount; Golem-born can't take
Relics).
8. **Exit decision** (B6): Cash Out, set Last Stand, or persist.
## B4. The Maw
After all turns in a round `r`:
```
speed = base_rate + accel * r + dread_push * total_dread_this_round
accum += speed
while accum >= 1.0 and frontier >= 1:
accum -= 1.0
collapse(frontier) // score rooms (B5) + Take parties on this floor
frontier -= 1
```
`collapse(f)`: for each room on `f`, the owner with the greatest summed marker strength
takes `base_value × f` Glory (ties split, floor-divided). Then every **active** party
whose `floor == f` is **Taken**: if `last_stand`, score the **Worthy End** bonus
(`f × worthy_factor + held_value`, ×1.5 Wardancer); else lose unbanked loot + all its
active markers (a **feel-bad** event, counted). The party is removed; the player may
draft a new one next round at the Threshold.
## B5. Scoring
Glory accrues from: room majorities at collapse, Cash-Out banks (loot + depth
dividend), and Worthy-End bonuses. At game end (frontier < 1), any still-active party is
force-Taken under the same B4 rule (Last Stand pays; otherwise unbanked loot is lost).
Highest total Glory wins; we also record the **margin** (winner runner-up).
## B6. Controllers (the bots under test)
Each is a pure function of the public game view. Risk appetite and exit timing are the
levers that distinguish them.
- **CashOut (Cautious).** Targets a moderate depth; presses on rarely; **Cash-Out** as
soon as banked-worthy loot clears a small bar *or* the Maw is within `safe_margin`
floors. Rarely sets Last Stand. Models the safe Decline player.
- **PressOn (Greedy/Engine).** Recruits hard early; targets rooms to hold; presses
aggressively; **persists**, fleeing upward only when the Maw is one floor away; Cashes
Out only when forced. Models the engine-builder who risks the clock.
- **WorthyEnd (Martyr/Diver).** Rushes to the deepest reachable floor; sets Last Stand
as soon as it holds a Vault/Throne (or is Wardancer); presses hard; rides the floor
into a **Worthy End**. High variance, high ceiling.
- **Balanced (Adaptive).** Reads `collapses_until_my_floor` vs. banked value: Cash-Out
when the Maw is close *and* it has a worthwhile bank; opportunistically converts to a
Worthy End when deep and doomed; otherwise presses moderately and grows. The
"good human" baseline.
## B7. What we measure (written to CSV/JSON; see `analysis/`)
- **Win rate by strategy** (mixed tables) dominance check (target: no strategy >
~40% in 4-player, none < ~12%).
- **Win rate by Lineage and by Calling** → asymmetry fairness (flag any > ~1.6× the
mean).
- **Game length** (rounds) → pacing (target band ~814).
- **Victory margin** as a fraction of the winner's score → runaway check (lower = tighter).
- **Feel-bad rate**: Takes-without-Last-Stand per game → keep low.
- **Cash-Out timing spread**: distribution of the depth/round at which winners cashed
out → is the Decline decision genuinely live, or is there one correct moment?
---
# Part C — Revisions after adversarial review (v2, what the engine implements)
Two independent reviewers (a "fun/decisions" lens and a "balance/exploits" lens) tore
into v1 before any code was written. They converged on the same structural flaw, so v2
folds the high-leverage fixes in. **The engine implements v2.** The deltas:
1. **Value rises with the Maw (the big one).** *Problem:* `glory = base×depth` +
bottom-up collapse correlated value with time — the climax landed mid-game, the
endgame fizzled on cheap floors, and the Worthy-End catch-up valve closed in the
back half (no deep floors left to dive into) → a back-half runaway, contradicting
the whole pitch. *Fix:* on collapse, a fraction (`value_rise_frac`) of each room's
**unclaimed** value rises to the floor above as "refugee plunder." The Maw's
frontier is therefore always the swelling hot zone, all the way up — the **final
Floor-1 collapse is now the biggest scoring event**, the arc crescendos, and a
trailing player can always dive into the (now-shallow) frontier for a real comeback.
2. **Re-entry near the frontier.** *Problem:* a fresh party from the Cash-Out loop
starts at Floor 1 and can never reach the deep live floor before it collapses →
Cash-Out self-limited to shallow farming; deep game decided once, early. *Fix:*
re-entering parties insert at `max(1, frontier reentry_gap)` — the upper floors
are eaten anyway, so new delvers drop in near the action. Keeps every party relevant.
3. **One collapse per round (carry the remainder).** *Problem:* `while accum ≥ 1.0`
could eat two floors in a round late-game, Taking "I'll flee next turn" parties with
no Last Stand — a systematic feel-bad. *Fix:* at most `max_collapses_per_round`
(default 1) collapses per round; the overflow carries in `accum`. "One floor away"
is now an honest promise.
4. **Worthy End = burst + denial, not a bigger Cash-Out.** *Problem:* Cash-Out did
almost everything Worthy-End did, safely and repeatably, so the triangle was a
binary and Worthy-End read as the loser's button; also a `held_value` double-count
risk. *Fix:* the two exits now have **non-overlapping** upside. **Cash-Out = the long
game** (loot + depth dividend + Fallen markers that keep earning). **Worthy-End =
burst + denial**: a guaranteed `worthy_factor × deepest_floor` spike *and the Maw
consumes your held rooms clean* — their value is removed from the board so no rival
inherits it. Now a *leader* takes a Worthy-End to deny a chaser a 36-point Vault.
`worthy_held_bonus` is **0 by default** (no double-count); it's a separate knob.
5. **Variable-strength claims + assault progress.** *Problem:* contest-by-marker-visit
was solitaire and un-reactable; deep claims were a ~5% one-turn lottery with no
floor. *Fix:* a claim's marker **strength = 1 + symbols invested** (Warden ×2), so
majority is a live bid; and unmet Steel/Cunning **accumulates as assault progress**
on the room across turns, so a near-miss is never a total zero.
6. **Lineages get behavioral hooks; dead cells fixed.** *Problem:* Lineages were flat
stat tweaks (no "two halves surprise you"), and some combos negated themselves
(Golem-born Prospector locked out Relics; Gnoll Reaver / Revenant Hierophant stacked
the same axis). *Fix:* Dvergar **ignores room threat** on its claim (smash-through —
now Dvergar Lorekeeper cracks guarded Vaults); Revenant **converts Dread→Mettle**
(peril as fuel, a decision, not redundant immunity); Golem-born's cost is now **1
hand size** (orthogonal — and it *cancels* Prospector's +hand, no dead cell).
See `content.rs` for the final tables.
7. **Sub-linear depth + comeback rubber-band, both as knobs.** Room glory is
`base × (1 + depth_coeff × (depth1))` (`depth_coeff` tunes the cliff), and
`comeback_bp` optionally scales a trailing party's exit payouts by its glory deficit.
Both default conservatively and are what the sweeps actually move.
These are the hypotheses the sim must now confirm: that the triangle is live (no exit
> ~45% of winners' choices), that deep rooms are *contestable but not free*, that the
runaway is dead (tight margins; real comeback rate), and that the feel-bad is rare.
---
## B8. What the sim deliberately does *not* model
Intra-floor geometry & corridors; denizen reputation as a full subsystem (folded into a
threat modifier); trading/negotiation (no table talk to simulate); the exact card/draft
UI. These don't move the six questions above, and adding them would trade clarity for
false precision. The balance levers that *do* matter all live in `configs/default.json`
and in the Lineage/Calling tables in `deepfall-core/src/content.rs`.