feat(deepfall): live 6-agent playtest + playtest-driven fixes

Ran an observed 6-player game with persona AI players (one per
Lineage/Calling) via a Workflow (72 agents): avgFun 7.3/10, 6/6 would
replay, tight 84-82-75 finish. PLAYTEST.md captures the session +
designer's assessment.

Findings the Monte-Carlo bots structurally missed:
- the careful/safe line feels UNFAIR, not just weak (banker finished
  last and resented it) — three human angles confirm the sim's
  Cash-vs-Worthy substitution finding
- humans hit the no-Last-Stand feel-bad 6x where rational bots hit it 0x
- deep-collapses-first geometry confuses newcomers (component fix)

Fixes (re-validated, 1000 seeds): WorthyEnd strategy 46.7 -> 42.5%,
safe lines lifted, 6p margin to 9.5%.
- Desperate Strike: a no-Last-Stand Take salvages 1/3 unbanked loot
- Extraction bonus: +3 glory per crew retired alive
- RULEBOOK: "Maw eats NEXT" indicator as a component rule

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Parley Hatch 2026-06-19 01:03:35 -06:00
parent c2420f8cd6
commit 1a712e0c22
5 changed files with 125 additions and 2 deletions

99
deepfall/PLAYTEST.md Normal file
View file

@ -0,0 +1,99 @@
# DEEPFALL — Live 6-Player Playtest (2026-06-19)
A complement to the Monte-Carlo balance sweeps (`FINDINGS.md`): six **persona AI
players** — each a different *kind* of human, each piloting a different one of the six
Lineages and six Callings — sat at a virtual table and played a full game, making real
in-character decisions refereed by a faithful rules engine, then reflected on the fun.
This is the thing the bots can't measure: **how it *feels*.**
## The table
| Player | Archetype | Company | Final |
|---|---|---|---|
| **Vex** | the spoiler (competitive, denial) | Dvergar Hierophant | 🥇 84 |
| **Rook** | the gambler (chaos, push-luck) | Sylphid Wardancer | 🥈 82 |
| **Mina** | the min-maxer (cold optimizer) | Wisp Prospector | 🥉 75 |
| **Pip** | the first-timer | Gnoll Reaver | 59 |
| **Tav** | the roleplayer (story over winning) | Revenant Lorekeeper | 50 |
| **Greta** | the cautious banker | GolemBorn Warden | 30 |
11 rounds, clean ending on Floor 1. Top three within **9 glory** (84-82-75), 2 lead
changes, 5 contested collapses, 2 Worthy Ends.
## The verdict
**avgFun 7.3/10 · 6 of 6 would play again.** The game is *fun* — its problems are
balance and clarity, not fun. The drama landed exactly where it was designed to:
- **Rook's round-8 Last Stand** — Maw one tick away, zero loot, stands up and declares
anyway, rides the Hall collapse to 22 glory while a rival is swallowed for nothing:
*"The Maw blinked first."* Their favorite moment of the night. **The three-exit triangle
produces real table theatre.**
- **Pip (the newbie) literally screamed** when a Den he'd half-forgotten he was holding
paid out 10 glory at collapse. *"I did not understand what happened. Everyone looked at
me. Worth it."* **The collapse-scoring surprise is delightful even when you don't fully
get the rules.**
- **Tav (the roleplayer)** stood in a burning Library as the floor fell and walked out
with 24 glory of "ancient marginalia." The theme carries.
## What it caught (and how it maps to the bot data)
**1. The careful line is under-rewarded — and it feels *unfair*, not just weak.**
Greta never lost a crew and finished *last*: *"The scoring punishes the careful and
rewards the reckless. That is not a game, that is a bad lesson."* Mina played the
provably-optimal cash-out line, found it **joyless** (*"that's not daring, that's
invoicing"*), and still lost to riskier players. Even **the winner, Vex**, flagged it:
*"Rook got eaten twice and still finished two points behind me — that gap is not
satisfying."* → This is exactly the sim's `CashOut`-strategy weakness (2.8%) and the
open Cash-vs-Worthy *substitution* finding — now confirmed from **three independent human
angles**, with the emotional weight the win-rate number can't convey.
**2. Humans hit the feel-bad 6× where the bots hit it 0×.** The Monte-Carlo bots cash out
defensively and almost never get Taken without a Last Stand (0.00/game). But the
*gambler*, the *roleplayer*, and the *newbie* walked into the silent death **six times**
this game. Rook and Tav independently demanded the same thing: *"Every death deserves a
punchline."* **This is the headline meta-finding: the live-agent playtest caught a problem
the perfect-play simulator structurally hid.**
**3. The deep-collapses-first geometry confuses newcomers.** Pip, round 5: *"Nobody told
me the floors count DOWN. I thought it went up. Why does it go down. WHO DESIGNED THIS."*
Got caught thinking he had five turns when he had one. → The fun-critic predicted exactly
this ("richest = doomed first" fights loss-aversion intuition); the playtest makes it a
hard **component requirement**.
**4. Contests feel flat.** Mina and Vex both noted that piling markers into a room is a
free hedge and ties split evenly: *"two markers beats four markers because the split is
flat — that is irrational"* (Mina); *"flooding a room should be a genuine gamble"* (Vex).
## Changes made (and re-validated in the sim)
| Change | Why | Knob |
|---|---|---|
| **Desperate Strike** — a no-Last-Stand Take salvages ⅓ of unbanked loot | Rook & Tav: a silent death shouldn't be a humiliating zero | `taken_salvage_frac = 0.33` |
| **Extraction bonus** — flat +3 glory per crew retired alive | Greta: reward surviving the delve, not just dying deep | `cashout_extraction_bonus = 3.0` |
Re-sweep (1000 seeds): the strategy band tightened — WorthyEnd **46.7 → 42.5%**, the
safe/engine lines lifting (PressOn 25.8 → 31.9, CashOut 2.8 → 3.7); 6-player victory
margin fell to **9.5%** (even tighter top-of-table). Pacing, combo spread, and the
0.00-feel-bad-for-bots all held.
## Recommended next (not yet built)
- **Marker-weighted contest shares** (Mina) — split a contested room's Glory in proportion
to marker strength, and hand every contestant a Dread token (Vex) so piling in is a real
gamble. The cleanest fix for finding #4, and it further differentiates Warden.
- **The "Maw eats NEXT" indicator** (Pip) — a big, unmissable physical marker on the next
floor to collapse. A *component* fix for the onboarding cliff; folded into the rulebook.
- **The deeper Cash-vs-Worthy fix** (finding #1) — the flat extraction bonus helps but
doesn't fully solve it. The real lever (per `FINDINGS.md`) is making *denial* score for
you, or paying Worthy End on *cumulative* depth dived. The next prototype.
## The bottom line for 6 humans
It **scales** — a clean 11-round game with a tight 3-way finish and real lead changes —
and it **delivers the fantasy**: the gambler got their blaze of glory, the newbie got their
scream, the roleplayer got their story, the spoiler got their win by playing the table.
With the survival incentive, the Desperate Strike, and a big "Maw-eats-next" marker, this
is a table-ready 6-player game. The one thing still worth a design pass before a print run
is making the *careful* player feel as clever as the reckless one feels alive.

View file

@ -239,6 +239,11 @@ fuel. This is your in-game engine (Quarriors' dice-building).
## 9. The Maw & player count
> **Component rule (playtest-critical):** the Maw marker must **loudly flag the next floor
> to collapse.** New players reliably forget the dungeon collapses *bottom-up* and think
> they have more time than they do — give the Maw a big, unmissable "EATS THIS FLOOR NEXT"
> pointer. (See `PLAYTEST.md`, finding #3.)
The Maw rises **1 floor per round**, **+1** whenever the shared **Dread track** fills (then
it resets). Players add Dread to the track every time they **Press On** and whenever
uncanceled ☠ is rolled — so the table's collective greed literally pulls the doom down

View file

@ -19,6 +19,8 @@
"cashout_depth_dividend": 2.0,
"cashout_min_bank": 12.0,
"cashout_extraction_bonus": 3.0,
"taken_salvage_frac": 0.33,
"worthy_factor": 2.5,
"worthy_held_bonus": 0.0,

View file

@ -47,6 +47,12 @@ pub struct Config {
pub cashout_depth_dividend: f64,
/// The Cautious bot Cashes Out once its bankable value clears this bar.
pub cashout_min_bank: f64,
/// Cash-Out flat bonus per crew retired "with boots on" — a survival incentive the
/// 6-human playtest demanded (the careful banker finished last and felt cheated).
pub cashout_extraction_bonus: f64,
/// Fraction of unbanked loot salvaged when Taken with **no** Last Stand — the
/// "Desperate Strike" so a silent death isn't a humiliating zero (playtest ask).
pub taken_salvage_frac: f64,
/// Worthy-End spike = `worthy_factor * deepest_floor` (+ optional held bonus).
pub worthy_factor: f64,
/// Extra Worthy-End credit per point of held room value. **0 by default** to
@ -97,6 +103,10 @@ impl Default for Config {
// shallow parties) and cratered to a 1.8% win rate; banking richer hauls
// before retiring makes the safe line viable.
cashout_min_bank: 12.0,
// Playtest-driven (2026-06-19): reward surviving the delve, and give a silent
// death a small consolation. See PLAYTEST.md.
cashout_extraction_bonus: 3.0,
taken_salvage_frac: 0.33,
// v2 (iter 3-4): 4 -> 2.5. At 4 the generic Worthy End (any deep party on a
// Throne) made the WorthyEnd strategy a 45% king; lowering the base while
// raising the Wardancer multiplier (now 2.2x) shifts the payoff onto the

View file

@ -542,7 +542,9 @@ impl Game {
/// Cash Out: bank loot + depth dividend, convert held markers to Fallen, retire.
fn cash_out(&mut self, pi: usize) {
let party = self.players[pi].party.take().unwrap();
let raw = party.unbanked + self.cfg.cashout_depth_dividend * party.deepest as f64;
let raw = party.unbanked
+ self.cfg.cashout_depth_dividend * party.deepest as f64
+ self.cfg.cashout_extraction_bonus;
let banked = raw * self.comeback_mult(pi);
self.players[pi].glory += banked;
self.players[pi].cashouts += 1;
@ -598,7 +600,12 @@ impl Game {
}
}
} else {
// The one true feel-bad: lose unbanked loot and all active territory.
// Taken with no Last Stand. Still the worst exit (territory lost, no depth
// dividend), but a "Desperate Strike" salvages a fraction of unbanked loot so
// it isn't a humiliating zero — the playtest's most-requested change.
let salvage = party.unbanked * self.cfg.taken_salvage_frac;
let cm = self.comeback_mult(pi);
self.players[pi].glory += salvage * cm;
self.players[pi].takes_no_laststand += 1;
for (d, ri) in &party.held {
let fi = Self::floor_idx(*d);