M2 capstone — the difficulty curve reproduces: Keep on the Shadowfell
the project's first designed experiment · zones: 3 × 50 seeds (06:43Z) · areas: all 19 × 30 (15:41Z, full power) · level-1 five-PC party, both sides Aggressive
The question behind this benchmark was the project's biggest: does the
honest engine reproduce the difficulty the books designed? Keep on the
Shadowfell is the classic published adventure — a village tutorial outside,
a keep that punishes under-leveled parties inside. We assembled each zone's
full encounter and ran it 50 times. The answer on first contact: yes,
and the ordering is monotone in XP. At 450 XP the level-1 party wins
comfortably; at 2,650–3,850 XP — eight times the threat — it wipes, and the
wipe rate orders with XP exactly as the book intends: the village is the
tutorial, the keep demands leveling.
Confirmed, not just reproduced. The declared re-run trigger fired
the same day — a 3-of-5 hero re-stat plus an action-delivery engine change. All 150 re-run
encounters match the first pass to the outcome vector: 37/0/13 · 0/24/26 · 0/32/18 —
74% / 48% / 64%, identical. The monotone-in-XP curve is doubly pinned.
74%
player wins in the village (37/50) — the tutorial is winnable at level 1
0% → 64%
wipe rate, monotone in XP: village → deep → upper
8.6×
XP threat, village 450 → upper 3,850 — one fixed level-1 party against all of it
Outcomes by zone — the curve
player winswipes (enemy wins)turn-capped
Outcome shares per zone (50 seeds each, both sides Aggressive), ordered
by XP budget. Zero-width segments are absences, not rounding: the village records
no wipes at all; neither keep zone records a single level-1 player win.
zone
XP
monsters
player wins
wipes
capped
rounds (mean)
village
450*
5
37 (74.0%)
0 (0%)
13 (26%)
10.5
shadowfell-keep-deep
2,650
11
0 (0%)
24 (48.0%)
26 (52%)
8.1
shadowfell-keep-upper
3,850
16
0 (0%)
32 (64.0%)
18 (36%)
9.3
* as-summed at run time: the manifest's 450 covered the four statted
kobolds; the Orcus Underpriest (level 5) also fielded in the village carried null XP in
the manifest — its unit data says 200. The pre-build audit caught the gap; the manifest was
corrected the same day (2b06191: underpriest 200, village budget 650). The encounter that
fought never changed — only the accounting did. The spread with corrected budgets: 650 →
2,650 → 3,850, ordering untouched.
The full 19-area curve — the fifth pin
The zone benchmark's caveat was "zones aggregate their areas." The OCR
and roster passes landed, and the complete pre-registered test ran: all
19 areas, n=30 each, full power (66/66 monster refs resolve). The shape
is the book — early delve areas near-100% for the level-1 party, a mid-band
grind, and the deep keep at 0–10%, death for the unleveled, exactly as
published.
ρ = +0.873
Spearman XP-vs-difficulty across the 15 XP-carrying areas — H1 confirmed strongly (registered threshold: > 0.4)
H2 ✓
every XP ≤ 400 area ≥ 36.7% win; every XP ≥ 800 area ≤ 30% — band checks pass, no span anomalies
10/105
pairwise inversions — all between identical-XP areas (325 vs 325, 175 vs 175): tie noise, not curve violations
Player win rate vs XP budget, all 19 areas (30 seeds each; the
four zero-XP skeleton-family areas run but sit outside the stats). Win
rates re-derived from the per-area results.json before this chart — all
19 match the README exactly; XP values are the assembly's own budgets.
Two honest flips, both printed. The first analysis pass
ranked win-rate and printed ρ = −0.873 — the registration defines difficulty
as 1 − win; the sign was self-caught and corrected before publication. And
the preliminary run's H3 failure (a span anomaly) did not survive the drift
fix plus full data — the flip is reported in both directions. Only one span
remains noteworthy: area 12 wins 36.7% where its identical-XP neighbor 17
wins 60% — flagged for the content lane, not hidden from the curve.
Damage — how each zone feels
party damage / encmonster damage / enc
Damage dealt per encounter by faction (mean ± sd over 50 seeds). The feel of
each zone in one glance: the village is a rout (166 v 37), keep-deep is mutual attrition
(109 v 105), keep-upper leans on the party (138 v 115) — with single-seed party damage
peaking at 259, enough to chew through a full level-1 party's hit points.
Two method finds the benchmark surfaced
1. The 50-turn cap was hiding the entire result. The keep zones
field 16–21 units; a fixed 50-turn limit resolved almost nothing — the first pass came
back 100% capped, a wall of "no verdict". Scaling the cap to unit count (10u→120,
16u→192, 21u→252) let the fights finish, and the curve appeared. A benchmark that
measures nothing but its own ceiling isn't a benchmark.
2. Encounter mode must read the assembled map, not the manifest
rows. Faction joins in `--encounter` initially fired against the raw manifest lists;
once they resolve from the assembled map, both sides field who actually stands on the
board. Small harness bug, total difference to every result.
What this does not claim — and what unlocks it
Zones aggregate their areas. The book intends per-area encounters (9–7 areas
per delve); the manifests carry area lists but not per-area rosters yet. Zone-level is
the coarsest honest cut — the per-area pass (OCR/render of the roster pages) will
move every number and is the real curve test. The party is fixed at level 1 across
all zones: this is each zone's un-leveled difficulty. The book-true comparison — a
party leveled per delve — needs the level-2/3 five-PC sets, which is content-lane work
the PC builder (#46) now feeds.
Terrain stays off at this phase (standard-fan placement, variant overrides
informational).