← Module 9/Flow and difficulty curves
RU
Module 9 · Game design theory

Flow and difficulty curves

Engagement is a moving balance between challenge and skill. Too hard — anxiety and quitting; too easy — boredom and quitting; just right — flow, where time disappears. Skill grows as the game goes on, so difficulty has to grow with it — and not as a smooth line but as a sawtooth: teach → let them practice → raise the bar.
~16 min🎲 design + 🔬 control🏠 lab
The gist in 30 seconds
Flow (Csíkszentmihályi) is a state of complete absorption; it needs clear goals, immediate feedback and challenge matched to skill. Plot challenge (y) against skill (x): above the diagonal is anxiety (too hard), below it is boredom (too easy), along it is the flow channel. Skill grows over time, so fixed difficulty falls out of the channel downwards (into boredom) — difficulty has to grow with skill. Two philosophies: DDA, which adapts to you (Mario Kart: rubber-banding keeps the race close), and difficulty-as-a-choice (Dark Souls: over-levelled or under-levelled is your decision). The real curve is not a smooth line but a sawtooth: introduce a mechanic (a dip into easy, you are a novice again) → practice → peak → the next mechanic → another dip. Celeste is the canonical teacher. Designing flow means managing the "challenge − skill" gap over time.

The mechanism: the channel, the gap and the sawtooth

The flow channel

Flow requires three conditions: clear goals (you know what to do), immediate feedback (you know whether it worked) and a challenge↔skill balance. On the skill×challenge plane that is a diagonal corridor:

skill → challenge → ANXIETYtoo hard BOREDOMtoo easy FLOW CHANNEL

Too easy is boredom (not engaged). Too hard is anxiety (frustration, quitting). Just right is flow (absorbed, time disappears). The channel is a band, not a line: there is tolerance. Formally, if c is challenge, s is skill and w is the width of the tolerance:

flow⇔|c−s|≤w

Anxiety at c−s>w, boredom at s−c>w. An example (on a 0–10 scale): skill s=6, tolerance w=2 → flow at a challenge of c∈[4,8]; c=9 is anxiety, c=3 is boredom.

Skill grows → difficulty has to grow

Skill s(t) increases as you play. A fixed challenge of c= const ends up below the channel an hour later → boredom. So c(t) has to track s(t). Hence the "optimal difficulty curve" rises. But how to make it rise — two approaches.

DDA vs difficulty-as-a-choice

DDA (dynamic difficulty adjustment) — the game adapts the challenge to your measured skill on its own. Mario Kart's rubber band: the leader is slowed a little, the last place is sped up → the race is always close → the player feels the tension (flow). The risk: too visible a rubber band feels unfair — "I am being punished for playing well", which kills motivation. Difficulty-as-a-choice (Dark Souls is not DDA): the player sets the difficulty through their level, build and summons. Over-levelled and the boss is trivial; under-levelled and it is brutal. The philosophy is "difficulty is a choice": some grind to over-level, others deliberately go in under-levelled. Both are valid.

The real curve is a sawtooth, not a straight line

Introducing a new mechanic resets perceived difficulty: on that mechanic you are a novice again. So the curve is a sawtooth: a local dip into easy at every new element, then a ramp upward; the overall trend rises. Celeste is the benchmark: it teaches a mechanic (the wall) → easy practice in a safe area → a hard challenge → introduces the dash → dip-practice-peak again. Plus Assist Mode (game speed, invincibility) — it widens the channel for accessibility without shaming the player.

Anti-patterns: flat difficulty (boredom); a spike with no teaching (anxiety, quitting); fake difficulty (just +HP on the enemy instead of more interesting behavior — BG3 on high difficulty adds enemies and tactics, not only hit points).

🏠 Lab — the flow channel, live
An interactive lab with no code: set the rate of skill growth and the channel width, pick a difficulty strategy (flat / linear / Celeste sawtooth / Mario Kart DDA) — and watch how much time the player spends in flow, in anxiety and in boredom. The sawtooth keeps them in the channel; flat slides into boredom; naive DDA risks oscillation. Open the lab →
The formalism of the channel (|challenge − skill| ≤ w) and DDA-as-a-controller are in the deep-end tabs above; here it is about turning the knobs yourself.

🕹 What to play — and what to notice

Celeste sawtooth + assist

A textbook curve: every screen introduces one move, gives safe practice, then a peak. Assist Mode (speed, air dashes, invincibility) widens the channel for any skill level without removing the design.

🎮 Do: play a chapter and note on each screen what it introduces and where the peak is — you will see the teeth of the sawtooth. Turn on Assist Mode and feel the channel widen (a dip into anxiety becomes flow) without any "shame about easy mode".

Mario Kart DDA rubber-banding

Classic dynamic adjustment: the blue shell and speed boosts for the trailing racers keep the pack tight. It feels great when the race is close; it irritates when you sense you are being "held back" in first place.

🎮 Notice: race once in first place and once in last — you will see the game adapting to you (worse items for the leader, better ones for last place). Catch the moment the rubber band feels unfair — that is the price of DDA that is too visible.

Elden Ring / Dark Souls difficulty as a choice

No rubber band: you move the channel yourself — through level, build, summons and the order you take bosses in. Over-levelled → trivial; under-levelled → brutal; both are a valid experience.

🎮 Notice: stuck on a boss — try moving your own skill equivalent (levelling, a different weapon, help) instead of a difficulty slider. Feel that here the controller is in the player's hands, not the machine's.

Deep end · DDA as a control loopskippable

A controller with a hidden-variable estimate

DDA is feedback control: the hidden variable — skill s(t) — is not directly observable and gets estimated from noisy signals (deaths, completion time, retry count, accuracy). Then the actuator — difficulty c(t) — is moved to keep the error e=c−s near zero (the center of the channel). This is a P controller: c←s+kp·signal.

Why naive DDA oscillates and "shows"

Too high a gain kp → overshoot: the player wins slightly and it gets sharply harder, loses and it gets sharply easier, the system oscillates, and the player notices (the rubber band gives itself away). On top of that the skill estimate is noisy (one death ≠ a drop in skill), so you need filtering/smoothing and a dead zone (the channel width w = do not react while they are in flow). The best DDA is invisible: slow, with hysteresis, actuating not raw difficulty but its soft channels (spawns, drops, intensity). Resident Evil 4 is the textbook hidden example.

Deep end · design: the sawtooth, fake difficulty, accessibilityskippable

The sawtooth and kishōtenketsu

One tooth of the sawtooth = one kishōtenketsu cycle: introducing the move (a dip into easy) → development (practice) → the twist (the peak) → the reconciliation (combining all the moves). Every new move temporarily makes the player a novice — hence the local reset of difficulty on a rising trend.

Real vs fake difficulty

Fake difficulty inflates numbers (HP, damage) without demanding new decisions: the player does the same thing for longer. Real difficulty demands new decisions (new enemy patterns, combinations of mechanics, timing). A good "hard mode" changes behavior, not just multipliers (BG3, Doom Eternal). Fake difficulty pushes you into anxiety without any growth in skill — the worst of both worlds.

Accessibility widens the channel

Assist/accessibility modes (Celeste, TLOU2) widen w and move the lower boundary: game speed, aim assist, invincibility, checkpoints. This is not an "easy-mode crutch" but an acknowledgement that players differ in skill while everyone needs flow.

Analogy
Flow is like a personal trainer picking the weight. Too light and it is boring, with no growth; too heavy and you fail the lift and give up; a little above your current maximum and you grow and stay engaged. As you get stronger (skill grows) the trainer adds weight (difficulty grows), keeping you in the productive zone. DDA = a machine that senses your reps and changes the weight on the fly; Dark Souls = a rack where you load the plates yourself (and can choose brutal). A bad rubber band = a machine that makes it lighter exactly when you are trying hard — demotivating.
Why it matters
Flow is the base model of "engagement" and the most useful lens for pacing: how to lay challenge out over time so the player falls into neither boredom nor anxiety. And the engineering frame (estimate hidden skill from noisy signals, keep the "challenge − skill" error near zero, avoid falling into oscillation) is a genuine feedback-control problem that transfers one-to-one to adaptive systems.
🔁 Beyond games — where this transfers
The lesson is about an adaptive loop: keep the difficulty of the task at the current ability, estimating that ability from noisy signals.

ML / AI (your domain): the flow channel is literally curriculum learning: feed the model tasks matched to its current ability and raise the bar as it grows. Too hard → no learning signal/gradient (≈ anxiety); too easy → no new information (≈ boredom); learning peaks at "hard but solvable". Automatic curricula — self-play leagues (AlphaStar), PLR (prioritized level replay), teacher-student level generation — keep the agent in its flow channel. DDA as a P controller with a hidden-skill estimate ⇄ adaptive testing / IRT (the next question sits at the examinee's ability level, setpoint control) and bandit-based difficulty selection. The oscillation of naive DDA (too high a gain, a noisy estimate) ⇄ training instability at a high LR or with a noisy difficulty estimate — cured by smoothing, hysteresis (a dead zone = the channel width) and slow adaptation. And "challenge matched to skill maximizes learning" ⇄ the zone of proximal development and sampling the hardest-but-solvable examples for maximum gradient.

Education/EdTech: adaptive platforms (Duolingo, Khan) keep the learner in the zone of proximal development — the same setpoint between boredom and anxiety.

Control: any autoscaler/thermostat/PID — state estimation plus an actuator plus the fight against overshoot and sensor noise.

Principle: keep difficulty at the current ability; estimate ability from noisy signals with a filter and a dead zone; adapt slowly, otherwise the loop gives itself away and oscillates.

🔧 Run it and poke at it
🏠 The "flow channel" lab in the browser
Open lab-flow.html: turn the skill-growth and channel-width knobs and compare 4 difficulty strategies by the share of time spent in flow. Push the sawtooth past >80% flow; break DDA with a high gain and watch it oscillate.
🎮 Curve breakdown ~25 min
Play the first chapter of Celeste (or any platformer) and draw the actual difficulty curve screen by screen: where a move is introduced (the dip), where the peak is. Overlay it on your own Novgorod: where its teaching "teeth" are and where it risks flat boredom or a spike with no teaching.
Checklist: in the lab, pushed the sawtooth to a high % of flow and caught DDA oscillating; marked the sawtooth teeth on a real game; connected the flow channel to curriculum learning.
Connections
foundation
MDA and the core loop — the core loop has to sit inside the flow channel: challenge matched to skill on every turn.
next
Level design — kishōtenketsu is exactly one tooth of the sawtooth: how to teach and dose difficulty through a level.
adjacent
Game feel — "immediate feedback" is one of the three conditions of flow.
adjacent
Telemetry — the signals for estimating skill (deaths/time/funnels) and measuring where players drop out.
Questions worth asking
Why is the channel a band rather than a line? Doesn't it need an exact balance?
An exact line of c=s is both unattainable and unnecessary. First, skill is noisy: a player focuses, then slips, and the instantaneous s jumps — chasing it with exact difficulty means yanking it every second (oscillation). Second, a slight deviation upward gives a rush when you win (the joy of overcoming), and downward gives a moment of breathing room and mastery. A band of width w is the controller's dead zone: while the player is inside it, touch nothing. So designers aim not at the line but slightly above skill (a small positive gap — a "stretch") while staying in the band. Too narrow a w = nervous, obvious adjustment; too wide and the player has time to get bored or scared before the system reacts.
When is DDA good and when does it feel cheap?
DDA is good when it is invisible and actuates soft channels: spawn intensity, ammo and health drops, AI aggression, hints — so the player feels "I got lucky / I pulled it off" rather than "they went easy on me". RE4 hides its adjustment under the hood this way. It becomes bad when it is noticeable and punishes skill: you play well → the game abruptly gets harsher or takes the win away (Mario Kart's rubber band being too visible in first place), you play badly → everything suddenly becomes trivial. The reasons it feels cheap: a high gain (a sharp reaction), actuating difficulty directly instead of soft channels, no hysteresis. The rule: adapt slowly, invisibly, through indirect levers, and never cancel a deserved success — otherwise you destroy competence (the sense of mastery).
How does real difficulty differ from fake difficulty?
Fake difficulty inflates numbers without demanding new decisions: the enemy has twice the HP and you do the same thing for twice as long. That is boring and tiring rather than hard and interesting. Real difficulty demands new decisions: new attack patterns, combinations of mechanics, timing, resource management under pressure. The test is simple: "does hard mode change how I play, or only how much?". Doom Eternal and BG3 on high difficulty change enemy behavior and tactics (new decisions) — that grows skill. Damage sponges are fake: they push you into anxiety or boredom without teaching. In the channel's terms: real difficulty moves both c and (through learning) s; fake difficulty only moves c up and leaves s where it was.
How does this relate to curriculum learning in ML?
One to one. Learning is maximal when a task sits at the edge of the model's current ability: examples that are too easy give no new signal (the model is already right — "boredom", effectively zero gradient), and ones that are too hard give no learning signal (the model is always wrong, with no gradient toward a solution — "anxiety"). Curriculum learning explicitly feeds tasks in order of increasing difficulty; automatic curricula (PLR, AlphaStar's self-play leagues, teacher-student level generation) estimate current ability and select tasks in the agent's "flow channel" — which is exactly DDA, only the student is a neural network. Adaptive testing (IRT) does the same for humans: the next question is chosen at the ability threshold to maximize information. And the pathologies are shared: adapting difficulty (or the learning rate) too aggressively → instability/oscillation; cured by smoothing, hysteresis and a slow pace. Once you understand the flow channel in design, you already understand why "hard but solvable" is the optimum for learning.
Further reading