Difficulty modes: calibrating Normal with the balance probe (2026-10-15)
Difficulty modes: calibrating Normal with the balance probe
Section titled “Difficulty modes: calibrating Normal with the balance probe”Task 7 of the difficulty-modes plan (docs/superpowers/specs/2026-10-15-difficulty-modes-design.md).
Goal: pick the shipped defaults for the Normal knobs in sim/difficulty.gd so a new player sees
the first boss, while Hard stays today’s game exactly.
Acceptance (from the plan): Normal median survival >= 150 s AND boss seen in > 50% of runs, for
the median bot profile; Hard within noise of the 59.1 s baseline in
docs/research/2026-10-14-balance-1.1-proposals.md.
Result: passes on two independent 40-seed blocks. Normal median 282 s with the boss seen in 33/40 runs on both blocks (66/80, 82.5%). Hard is unchanged, seed for seed.
Method
Section titled “Method”- Tool:
tools/balance_probe/balance_probe.gd(taken frombalance-1.1-proposals, plus--difficulty=normal|hardand aboss seen N/Mcount fromsim.boss_log). Every run is a real headlessSoakRun(fresh save, Cobalt, skilled autopilot under a skill profile), 6-minute cap (--ticks=21600). - Knob values were swept with
--tun=Difficulty.NORMAL_*=..., which goes through the realTunables.applypath, so the same values work as a remote-config patch or a console tweak. - 40 seeds per arm. Seed block A is
--seed0=5000(the same block the proposals doc used); block B is--seed0=5040, used to check the finalists. - “Boss seen” means any boss entered the field (
boss_logis non-empty). In Survival the first boss cannot arrive before about 190 s: waves run forSim.WAVE_PHASE_S(180 s), the arena then has to be cleared, thenBOSS_PREP_S(10 s). So a “boss seen > 50%” target means most runs must survive past roughly 200-280 s. That bar is much higher than “median >= 150 s”.
Hard is the identity
Section titled “Hard is the identity”| Arm | Seeds | Median | p25-p75 | Dead < 30 s | Boss seen |
|---|---|---|---|---|---|
Pre-modes main (0784003d), original probe |
A | 58.7 s | 47.9-83.8 | 8/40 | n/a |
difficulty=hard on this branch |
A | 58.7 s | 47.9-83.8 | 8/40 | 0/40 |
difficulty=hard |
B | 49.6 s | 40.3-65.5 | 5/40 | 0/40 |
difficulty=hard, strong profile |
A | 112.6 s | 81.1-145.1 | 0/40 | 4/40 |
On block A the pre-modes commit and the Hard arm match on every seed (run time, death cause and
level are all identical). The 0.4 s gap to the doc’s 59.1 s, and its 2/40 deaths under 30 s
against 8/40 here, come from main changing between the proposals run (2026-10-14) and 0784003d.
They are not caused by the difficulty code.
Normal arms (median profile)
Section titled “Normal arms (median profile)”The knobs are listed only where they differ from the Task 1-5 starting defaults (START_FRAC 0.5, RAMP_S 120, SHOT 0.7, CHARGER_DMG 0.75, TELEGRAPH 1.33, XP 1.5 below level 8, BOSS_HP 0.75, BOSS_DMG 0.75, HIT_GRACE 1.5, MAX_HP 1.25).
| Arm | Knobs changed | Seeds | Median | p25-p75 | Alive at 180 s | Boss seen |
|---|---|---|---|---|---|---|
| n1 | (starting defaults) | A | 100.6 s | 83.5-133.7 | 6 | 4/40 |
| n2 | START 0.35, RAMP 240 | A | 114.0 | 90.4-146.6 | 5 | 4/40 |
| n3 | MAX_HP 1.5, GRACE 2.0 | A | 105.5 | 83.6-136.1 | 5 | 3/40 |
| n4 | XP 2.0, XP_LEVELS 12 | A | 97.4 | 84.3-112.8 | 2 | 2/40 |
| n5 | RAMP 240, MAX_HP 1.5 | A | 122.5 | 103.0-160.8 | 9 | 8/40 |
| n6 | n5 + START 0.3, CHARGER_DMG 0.5 | A | 122.4 | 99.7-153.6 | 5 | 4/40 |
| n7 | RAMP 360, MAX_HP 2.0 | A | 113.6 | 87.1-139.7 | 6 | 6/40 |
| n8 | n5 + START 0.3, SHOT 0.5 | A | 119.6 | 91.6-155.5 | 6 | 4/40 |
| n9 | RAMP 360, START 0.25 | A | 139.9 | 101.0-184.9 | 13 | 10/40 |
| n10 | RAMP 600, START 0.15 | A | 173.4 | 110.1-278.1 | 18 | 17/40 |
| n11 | n9 + MAX_HP 1.75, GRACE 2.0 | A | 152.5 | 111.8-264.6 | 15 | 12/40 |
| n12 | n9 + XP 1.0 (boost off) | A | 140.1 | 97.7-189.1 | 11 | 9/40 |
| n13 | RAMP 600, START 0.25 | A | 137.4 | 91.8-246.9 | 13 | 11/40 |
| n14 | n10 + MAX_HP 1.75, GRACE 2.0 | A | 195.4 | 138.1-292.0 | 22 | 19/40 |
| n15 | RAMP 600, START 0.1 | A | 263.2 | 156.5-281.7 | 26 | 24/40 |
| n16 | n10 + SHOT 0.5, CHARGER_DMG 0.5 | A | 264.2 | 166.5-298.1 | 27 | 26/40 |
| n16 | same | B | 198.5 | 142.7-274.1 | 20 | 19/40 (fails) |
| n19 | n16 with START 0.1 | A | 271.6 | 196.8-284.9 | 31 | 29/40 |
| n19 | same | B | 251.6 | 162.1-287.6 | 26 | 23/40 |
| n20 | n16 + GRACE 2.0, TELEGRAPH 2.0 | A | 282.0 | 242.1-306.3 | 35 | 33/40 |
| n20 | same | B | 282.0 | 257.6-308.2 | 34 | 33/40 |
After the defaults were written into sim/difficulty.gd, a rerun of block A with no --tun
matched n20 on every seed.
What the sweep says
Section titled “What the sweep says”- Field density is the lever that matters. The biomass opening ramp (start fraction and ramp length) moved survival more than everything else combined: n9, n10 and n15 each beat any damage, HP or grace change made at the original ramp.
- More XP does not buy survival. n4 (2x XP to level 12) levelled faster (12 at death against 9)
but died slightly sooner, and n12 (boost off) matched n9. Likely cause:
Sim._danger_mult()scales enemy HP with player level afterDANGER_START, so a faster level-up also toughens the field. The XP knob is kept at 1.5 below level 8 because it makes the early game feel faster. It is not doing survival work. - Max HP and hit grace help only once density is low. n3 and n7 (more HP at the original density) barely moved the median. n14 and n20 (the same kind of change on top of the low ramp) were the biggest late gains.
- One block of 40 seeds is not enough to call a pass. n16 passed block A at 26/40 and failed block B at 19/40. The bot’s boss-seen rate is bimodal, because a run either clears the wind-down or it doesn’t. n20 is the only arm that cleared both blocks with a margin.
- n19 (START 0.1) also passes, but 0.1 is the registry floor for that knob, which would leave no room on the console to go gentler. n20 keeps START at 0.15.
Chosen defaults (shipped in sim/difficulty.gd)
Section titled “Chosen defaults (shipped in sim/difficulty.gd)”| Knob | Was | Now |
|---|---|---|
NORMAL_BIOMASS_START_FRAC |
0.5 | 0.15 |
NORMAL_BIOMASS_RAMP_S |
120 | 600 |
NORMAL_SHOT_DMG_MULT |
0.7 | 0.5 |
NORMAL_CHARGER_DMG_MULT |
0.75 | 0.5 |
NORMAL_CHARGER_TELEGRAPH_MULT |
1.33 | 2.0 |
NORMAL_HIT_GRACE_MULT |
1.5 | 2.0 |
NORMAL_XP_MULT / NORMAL_XP_BOOST_LEVELS |
1.5 / 8 | unchanged |
NORMAL_BOSS_HP_MULT / NORMAL_BOSS_DMG_MULT |
0.75 / 0.75 | unchanged |
NORMAL_MAX_HP_MULT |
1.25 | unchanged |
GOLD_MULT_NORMAL / GOLD_MULT_HARD |
1.0 / 1.0 | unchanged (see economy) |
A ramp of 600 s means the field is at 15% of the Hard fill target at the start, about 40% at the 180 s wind-down, and only reaches full density at 10 minutes. Normal is gentler for the whole of a typical new player’s run, not only the opening. That is intended (Chris, 2026-10-15: “we have been tuning for Toby”), but it should get a human playtest.
Other profiles on the chosen defaults (no gate, recorded)
Section titled “Other profiles on the chosen defaults (no gate, recorded)”| Profile | Mode | Seeds | Median | p25-p75 | Boss seen |
|---|---|---|---|---|---|
| weak | Normal | A | 134.4 s | 108.6-178.8 | 6/40 |
| weak | Hard (proposals doc, older main) | A | 41.9 s | n/a | n/a |
| strong | Normal | A | 307.0 s | 283.4-360.0 | 40/40 |
| strong | Hard | A | 112.6 s | 81.1-145.1 | 4/40 |
Normal is not trivial for a skilled player. The strong bot still dies at a median of about 5 minutes, and fewer than a quarter of its runs reach the 6-minute cap. The weak bot more than triples its survival time, but it still rarely reaches a boss.
Economy check (skill bh-economy-sim)
Section titled “Economy check (skill bh-economy-sim)”tools/economy_sim now takes --difficulty=hard|normal (career_runner.gd, EconomyCareer,
and the fifth argument of scripts/economy-careers.sh). A Normal career writes
reports/economy/career-normal-<profile>-<seed0>.json. Hard stays the default, so existing
calibrations are unchanged. A GUT test (test_economy_career.gd) checks that a Normal career
really plays Normal runs.
Three median-profile careers per mode (seed0 10000/11000/12000, the default 20-minute run cap, run until the shop is complete). Gold rates are 1.0/1.0:
| Mode | Gold/min | Gold/run | Median run | Runs to 50% of shop | Runs to complete shop | Play time to complete shop |
|---|---|---|---|---|---|---|
| Hard | 220 / 208 / 205 | 1507 / 1265 / 1279 | 131 / 115 / 138 s | 14 / 17 / 14 | 16 / 20 / 17 | 110 / 121 / 106 min |
| Normal | 123 / 132 / 136 | 1212 / 1467 / 1188 | 461 / 441 / 415 s | 15 / 12 / 15 | 19 / 17 / 20 | 187 / 189 / 175 min |
Finding (flag for Chris, rate NOT changed): at 1.0x, Normal pays about 62% of Hard’s gold per
minute (about 130 against 211). Gold per run is about the same, so the number of runs needed to
finish the shop is about the same (17-20 against 16-20). Because Normal runs last about 3.5 times
longer, finishing the shop takes about 1.65 times as much play time (about 3.1 h against 1.9 h).
That is large enough to be Chris’s call. If the aim is equal gold per hour, GOLD_MULT_NORMAL of
about 1.6 would close the gap. If the aim is equal gold per run, 1.0 already does that, and Hard’s
reward is its shorter runs. It is a live tunable either way.
Caveat: the career sim starts each run from the career’s shop save, so these runs are already stronger than the fresh-save probe runs above. The Hard careers’ first five runs have medians of 78-96 s, not 59 s.
Caveats
Section titled “Caveats”- The bot is a proxy, not a human. The skill profiles were calibrated against early human
telemetry (
ai/skill_profile.gd). The bot dodges and prioritises differently from a person, and the economy-sim skill notes it is weaker than humans early on. Treat the numbers as a ranking of knob settings, not as predictions of human survival times. Confirm with a human playtest on Normal. - A bigger hull also buffs max-HP-scaled weapons.
Difficulty.apply_run_startmultipliesmax_hpbyNORMAL_MAX_HP_MULT(1.25). Effects that scale with max HP grow with it, such as the Scar Tissue discharge (sim/weapon_scar_tissue.gd) and the Leech per-tick heal cap. This is a small confound in the Normal arms, mostly the strong one, where those builds are more common. The sweep never movedMAX_HP_MULTin the chosen arm. It stays at 1.25. - Boss-seen is bimodal (see n16). Any later retune should check at least two seed blocks.
- All values stay console/remote tunables (the “Difficulty” group). Any of them can be moved live or by a remote-config patch without a rebuild. A patch still needs Chris’s authorisation.
- 40 seeds per arm, 6-minute cap. The
alive@180and boss-seen counts are reliable to a few runs. Differences of about +-10 s in the median are within noise.