diff --git a/deepfall/PLAYTEST.md b/deepfall/PLAYTEST.md new file mode 100644 index 0000000..770c827 --- /dev/null +++ b/deepfall/PLAYTEST.md @@ -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. diff --git a/deepfall/RULEBOOK.md b/deepfall/RULEBOOK.md index 1e16b5b..eca574c 100644 --- a/deepfall/RULEBOOK.md +++ b/deepfall/RULEBOOK.md @@ -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 diff --git a/deepfall/configs/default.json b/deepfall/configs/default.json index f8ec736..058538d 100644 --- a/deepfall/configs/default.json +++ b/deepfall/configs/default.json @@ -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, diff --git a/deepfall/deepfall-core/src/config.rs b/deepfall/deepfall-core/src/config.rs index bd6cc86..2ceb1a7 100644 --- a/deepfall/deepfall-core/src/config.rs +++ b/deepfall/deepfall-core/src/config.rs @@ -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 diff --git a/deepfall/deepfall-core/src/game.rs b/deepfall/deepfall-core/src/game.rs index a5914d6..1458a5b 100644 --- a/deepfall/deepfall-core/src/game.rs +++ b/deepfall/deepfall-core/src/game.rs @@ -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);