thu.ndero.us Experiments in engine, systems, and iteration
Learning

The bar that moved with me

A melee hit in this game can be committed: swing fast enough and sweep wide enough at the moment of contact, and the blow does 1.5× damage, flashes, and makes a different noise. It is the one thing in combat that rewards…

A melee hit in this game can be committed: swing fast enough and sweep wide enough at the moment of contact, and the blow does 1.5× damage, flashes, and makes a different noise. It is the one thing in combat that rewards effort rather than positioning, and for about a week it quietly did not work.

Not broken. Working exactly as written, and teaching me to stop trying.

Why the gate was a percentile in the first place

The threshold started life as a number I picked in an evening: swing faster than this many
metres per second. That went stale almost immediately, and I have the numbers because the game writes them down. My median swing speed went 9.78, then 9.43, then 16.54 across three sessions — and then 12.5 to 47.0 over two days, once committed hits gained a flash and a sound.

That is the whole problem in one line. A reward changes the behaviour it measures. The
feedback taught me to swing bigger, so every constant chosen against the old distribution was obsolete by the following session. At one point more than half of all swings were sitting at the damage ceiling and I could not have told you, because a saturated band feels completely fine — it is only visible in the distribution.

So I made the threshold a percentile of my own recent swings, recomputed every swing. Same idea as the damage, haptic and flinch curves, which are all percentiles now for the same reason. It fixed the staleness perfectly and created a worse problem.

A ranking, not an achievement

If the bar is my own 65th percentile, then roughly a third of my swings clear it no matter how hard I swing, because "hard" is defined by what I do. Swing harder for a while and harder becomes the new baseline and the bar follows me up.

Short-term it is even worse than that. One heroic swing moves a 145-sample pool by almost nothing, so the immediate reward for effort is not small — it is zero. I wrote at the time:

it didn't feel like I had any choice in the matter of determining if it happened or not... I stopped trying to go for committed hits and just swung fairly normally.

Which is the mechanic teaching the exact opposite of what it exists to teach.

Every measurement said it was healthy

This is the part I want to keep. I had instrumentation on all of it — the game writes a JSON file every five hits with the medians, the spread, the gates and the rate. Every number said the band was fine: properly spread, no saturation, gates tracking the distribution.

The tell was only visible in the replay. Across one session my committed rate went 4-in-14 to 1-in-14 while the gate went down the whole time — speed threshold 44.65 falling to 41.06. The bar got easier, and the reward still evaporated, because I was falling away from it faster than it was falling toward me. And then I stopped pushing, which made it fall further.

No measurement of the band itself would ever show that. The band was healthy. I was the thing that had changed.

I also got the story wrong from the inside. I was fairly sure I had found a repeatable technique — sword resting on the shoulder, whipped down in an arc. The data says otherwise: my speed spread moved 6% and my sweep spread more than doubled. I was not converging on a move. I was swinging harder and wilder, and at that point nearly everything cleared the bar, which is why I could not say what had done it. That is a degenerate band reached from the player's side rather than from the constant's, and it looks like mastery while it is happening.

The split that came out of it

The fix is not "percentiles are bad". It is that I had been treating two different kinds of number as one thing:

| kind | example | what it must do |
|---|---|---|
| scaling — nobody chases it | damage, haptics, the flinch | never saturate |
| target — the player aims at it | the committed gate | be reachable, and stay still |

A live percentile is right for the first and wrong for the second, because a target that recedes as you approach it is not a target. So the committed gate now freezes the first moment there are enough samples in a session and never moves again until restart — while the session still feeds the history, so the next sitting re-bases against what I actually did. Fixed within a sitting, calibrated between sittings.

Worn, it works. The session after the freeze went 27% → 47% → 7% → 67% → 67% committed across 75 swings against a bar that never moved. The session before, with the same mechanic and a moving bar, went 27 → 47 → 13 → 13 → 13. Same numbers at the start, opposite shapes — the difference is that pushing paid.

Two smaller things I only found by measuring

The conjunction was never priced. The gate is speed and sweep, and I had retuned the
percentile twice without ever checking how correlated those two are. Measured over 145 paired swings: Pearson 0.330. A fast swing is only weakly a wide one, so 0.65 on each axis was delivering 10% where I had intended about a fifth. Dropping to 0.55 gives ~23%, and the actual thresholds barely move — which is the tell that it was never a threshold problem.

The memory was too long. The pool held 295 swings against a 400 limit, so nothing had aged out: 220 older, weaker swings were holding the bar down while I outran it. Since freezing gives up within-session adaptation on purpose, the between-session re-base is the only correction there is, and at 400 it could not respond. It is 150 now — about two sittings.

Still unresolved, and it is a design question rather than a tuning one: sweep is not
proprioceptive and speed is.
I know exactly how hard I swung and have no idea whether I swept 214° or 218°, so half the gate is a quantity my body cannot report. The near-miss cue can say "almost" but it cannot say "four degrees short". Weighting speed more, dropping sweep to a damage bonus, or splitting the cue per axis are genuinely different games, and I have not picked one.