I cannot play four hundred runs to tune a difficulty ladder, so a bot does it. The bot has nine knobs — how long it holds a decision, how much noise it adds to its heading, how far it keeps monsters, how hard it sidesteps a shot, how often it stands still — and for months I set them by hand and by feel.
The obvious improvement: record myself playing and fit the knobs to the recording. So the game now writes ten samples a second of my own play into a log — my move vector, my health, the crowd within 200 and 400 pixels, the nearest enemy shot and how squarely I move relative to it, the nearest hazard and whether I am standing in it, every cast. Those are exactly the observables the bot decides on, so each knob could be read off the log instead of guessed.
Ten runs, 38,882 samples, 65 minutes, the last one a win. Then I fitted the tier and raced it against the hand-tuned one on the same seed, same class, same rung:
| me, actually playing | 13 runs to clear the rung |
| bot, hand-tuned tier | 23 runs |
| bot, fitted to me | 33 runs |
| bot, fitted, with two knobs reverted | 23 runs |
Two knobs were the entire regression, and they failed for the same reason: the recorder and the bot use the same word for two different things.
idle: fitted 0.30, hand-tuned 0.03
Measured as the share of samples where I am not moving. That is a real number and it is 30%. But a human standing still is aiming, or holding fire, or reading a card screen, or waiting for a telegraph to resolve — standing still is an action.
The bot's idle means "skip this decision entirely and do nothing". At 30% in a
horde game that is just death, thirty percent of the time.
react: fitted 0.40 s, hand-tuned 0.15 s
Measured as the median seconds between heading changes of more than 0.6 radians. Also a real number. But a human moves in smooth arcs, and an arc registers as almost no discrete turns at all, so the statistic dramatically overstates my latency.
The bot's react means "hold the previous decision this long". Four hundred
milliseconds of committed movement in a game where the whole skill is repositioning.
The general shape
Both are the same mistake: an observable that describes a person does not automatically mean the same thing to the policy that consumes it. The seven knobs that measured as a wash — kite radius, dodge range, dodge weight, trap awareness, greed, hazard awareness, noise — were all direct physical quantities with the same meaning on both sides. The two that broke were the two that were summaries of behaviour.
I had already learned half of this and not generalised it. An earlier pass gave the "pro" tier a bigger kite radius and less greed, on the theory that a better player keeps their distance and does not chase pickups. The pro tier then arrived at minute four underlevelled and died earlier than the average one. In a survivors-like the strong player farms harder: they stand at the edge of the crowd and hoover every gem, and they pay for it in precision, not in distance. The pro tier is now the greediest one I have.
What is actually left
With the two knobs reverted the bot needs 23 runs where I needed 13, and the fit contributes nothing to that gap, because it fits movement and the residue is decisions — build order, which card to take, how to play a boss. So there is a second fitter now, and it needed no new recording at all: every run I have ever played is already in a telemetry log with the skills I bought, the cards I picked and where my coins went. The skill-point order comes from the difference between one run's ending skills and the next run's starting ones; the boon priority from every card ever picked; the shop order from where the coins actually ended up on the profile I play.
That last one is deliberately a distribution and not a sequence. Three attempts at
recovering a purchase order all failed on the same facts: the log spans several devices and
character resets, most old records do not carry the shop table, and pairs() has no
order to recover. The first version emitted four thousand rank purchases against a save that holds
one hundred and eight. What is unambiguous is where the coins ended up — damage 17, armor 10,
cast speed 10, luck 9 — so the bot now spends in my proportions from its first purchase. The
hand-written list it used before bought max HP second, which I have never done in my life.
And there is a number to judge all of it by now. A separate tool replays my own recorded runs: it builds the bot a character with my skills, my level, my shop ranks and my gear, on the same stage and difficulty, plays it a few seeded times, and prints the two side by side. On the first stage it scores 0.90 agreement — and it says the bot survives longer than me and kills 15–25% less. It plays too safely. Which is a much more useful sentence than "the bot dies too early", which is what I believed for a month.