reikhelm/deepfall/FINDINGS.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

157 lines
8.3 KiB
Markdown
Raw 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 — Simulation Findings
What the balance-by-bot rig actually said, after 9 tuning iterations over ~thousands
of seeded games per sweep. This is the honest post-mortem: what the design got right,
what the sim broke, what I fixed, and what's still open.
The deliverable was never "a perfectly balanced game." It was: **design a mashup,
then let a simulator tell me the truth about it.** It did.
---
## 1. The headline
Final config (`configs/default.json`), 2000 seeds × 24 seat-permutations per strategy
sweep, 2000 games per combo:
| Metric | Result | Verdict |
|---|---|---|
| **Game length** | 10.9 rounds (1011) | ✅ in the 814 target band; the Maw ends it cleanly, every time |
| **Victory margin** | 13.7% of winner's score | ✅ tight — **no runaway leader** |
| **Feel-bad rate** (Taken w/o Last Stand) | **0.00 / game** | ✅ the one true feel-bad is fully designed out |
| **Deep content reachability** | only **6.5%** of deep-floor claims by round-1 parties | ✅ the deep game is *not* decided at game start |
| **Combo spread** (best worst of 36) | **32.5 pts** (8.1%40.6%) | 🟡 every combo viable; still a soft Lineage/Calling tilt |
| **Strategy band** | WorthyEnd 46.7 / PressOn 25.8 / Balanced 24.8 / CashOut 2.8 | 🟡 aggression is the strongest line; pure passivity is weak |
Three of the design's central promises held up under simulation — **no runaway, no
elimination feel-bad, and a living deep game**. Two things stayed stubborn — the
draft has a soft power tilt, and the four play-styles aren't a flat band. Both are
understood (below), not mysterious.
---
## 2. The three biggest things the sim caught (and the fixes)
### A. Value and time were correlated → the arc fizzled and the back half ran away
Room glory was `base × depth` and the Maw eats deepest-first, so the richest rooms
scored *earliest*; the game crescendo'd in the middle and ended on its cheapest floors,
and the Worthy-End catch-up valve closed once the deep floors were gone. Both critics
flagged it before a line of code ran.
**Fix (the single best rule in the game):** on collapse, a fraction of each room's
*unclaimed* value **rises to the floor above** as "refugee plunder." The Maw's frontier
is now always the swelling hot zone, all the way to Floor 1. The data confirms it: the
claim distribution is **non-monotonic** — Floor 4 (162k claims) draws *more* than Floor 3
(96k), because value pools wherever the frontier sits, and Floor 1 draws the most of all
as everyone converges for the finale. The endgame crescendos instead of fizzling, and
margins stayed tight (13.7%).
### B. The deep game was nearly a game-start-only contest
A fresh party from the Cash-Out loop entered at Floor 1 and physically could not reach
the deep live floor before it collapsed (the balance critic walked the move-math). So
the Decline loop would have fed you parties that could only farm shallow rooms.
**Fix:** re-entering parties insert at `max(1, frontier 1)`. Result: only **6.5%** of
deep-floor claims come from round-1 parties — i.e. **93.5% of the deep game is played by
later-drafted crews.** The Decline loop stays relevant the whole game.
### C. The multi-collapse feel-bad
`while accum ≥ 1.0` could eat two floors in one round late-game and Take a "flee next
turn" party with no Last Stand. **Fix:** cap one collapse per round (carry the
remainder). The feel-bad rate is a flat **0.00** across every config tested since.
---
## 3. The balance-by-bot trajectory
Each row is a tuning pass; the simulator drove every decision. Regressions are left in
on purpose — they're the method working (a change that *looked* good and the data
vetoed).
| Iter | Lever changed | Combo spread | Weakest combo | WorthyEnd / CashOut strat |
|---|---|---:|---:|---|
| 1 | initial design | 77.8 | 0.5% | 41.6 / 1.8 |
| 2 | sub-linear depth; neuter `ignore_threat`; mixed combo field | 59.8 | — | 44.8 / 0.0 |
| 3 | Champion-recruit Wardancer; Cautious plays the frontier | 46.5 | — | 44.8 / 3.1 |
| 4 | half-strength Fallen markers | **56.2** ⚠ | — | 44.0 / 3.5 |
| 5 | revert Fallen; cap marker overflow; raise dividend | 47.2 | — | 37.0 / 5.0 |
| 6 | bots compare worthy-vs-bank payouts | **55.5** ⚠ | — | 38.5 / 4.2 |
| 7 | Wardancer +1 marker strength; revert dividend | 43.4 | 4.9% | 46.1 / 2.8 |
| 8 | Dvergar 1 Cunning; Wisp gets a Warrior | 35.9 | 5.0% | 45.4 / 2.8 |
| 9 | Dvergar 1 Faith (gentler); trim Reaver | **32.5** | **8.1%** | 46.7 / 2.8 |
Net: combo spread **58%** (77.8 → 32.5), weakest combo **×16** (0.5% → 8.1%), and the
dead cells (Sylphid ~1%, Wardancer ~0.3%) all revived into the playable band.
The two ⚠ regressions taught real lessons: half-strength Fallen markers (iter 4) handed
Warden's ×2 markers an immortal hold and spiked it to 56%; and making bots *compare*
exit payouts (iter 6) — correct in itself — exposed that Wardancer's whole value was a
rare conditional, which is what forced the iter-7 fix.
---
## 4. Final combo heatmap (focal combo as Balanced vs. a mixed random field)
```
Reaver Lorekeeper Wardancer Hierophant Warden Prospector
Dvergar 40.6% 33.7% 18.4% 32.5% 30.9% 31.6%
Wisp 20.6% 15.9% 11.9% 14.5% 14.4% 14.2%
Revenant 17.3% 13.9% 8.9% 12.2% 12.7% 11.7%
Gnoll 31.7% 28.7% 9.6% 23.9% 24.4% 24.8%
Sylphid 23.3% 18.8% 12.4% 15.0% 15.3% 14.6%
GolemBorn24.9% 20.4% 8.1% 19.6% 19.1% 32.0%
Lineage avg : Dvergar 31.2 Gnoll 23.9 GolemBorn 20.7 Sylphid 16.6 Wisp 15.3 Revenant 12.8
Calling avg : Reaver 26.4 Lorekeeper 21.9 Prospector 21.5 Hierophant 19.6 Warden 19.5 Wardancer 11.6
```
(Mean focal win-rate sits below 25% because the focal always plays the generalist
Balanced bot while the field includes the more aggressive WorthyEnd/PressOn strategies —
so read the *vs-mean* column in `combo_winrates.csv`, not the raw 25% line.)
---
## 5. What's still open (and why)
### The Cash-Out vs. Worthy-End substitution (the deepest finding)
The strategy band stays lopsided (WorthyEnd 46.7%, CashOut 2.8%) for a structural
reason the sim made undeniable: **for a glory-maximizing agent, Cash-Out and Worthy-End
are substitutes** — both convert a doomed party into points. So *every* lever that helps
the safe banker (e.g. the depth dividend) directly nerfs the martyr, and vice-versa.
They sat on a trade-off frontier across iterations 510: I could balance the *strategies*
or the *Wardancer calling*, but not both with the same `worthy_factor`.
The intended differentiator — Worthy-End's **denial** (the Maw salts your held rooms so
no rival inherits them) — is real in the rules but **invisible to the bots**, because a
win-rate maximizer doesn't model the opponent's loss, and you mostly salt rooms you were
soloing anyway. *This is the headline design lesson:* the two exits only become a true
triangle (not a binary) when denial is made to **matter to your own score** — e.g. score
denial explicitly (gain a cut of the value you deny), or make Worthy-End pay on
*cumulative* depth dived across the whole game (rewarding a play-pattern banking can't
copy). That's the first thing I'd prototype next.
### The soft draft tilt
Dvergar (threat-reduction is just broadly useful) and Reaver (loot-on-claim is a
flywheel) sit on top; Revenant and Wardancer sit low. No combo is dead (worst is 8.1%),
so the draft is *spicy, not solved* — acceptable for a Small-World-style game where some
combos are stronger — but a shipping version would want Dvergar's hook costed harder and
Revenant's Dread-to-Mettle made to actually pay.
### Pacing variance is too tight
Game length is 1011 rounds with almost no spread. Real push-your-luck wants more
variance in *when* the Maw breaks through. The `maw_dread_push` and `press_dread_push`
knobs are the levers; I'd widen them and accept a 914 spread.
---
## 6. Reproduce
```bash
cd deepfall
cargo test # 11 tests: determinism, pacing, no-double-count
cargo run --release -p deepfall-sim -- --out out --seeds 2000
python3 analysis/analyze.py out # the heatmap above, no dependencies
```
Every number here is a pure function of `(configs/default.json, seed)` — same input,
same output, on any machine.