
Progression Systems
- 39 installs
- 122 repo stars
- Updated January 22, 2026
- omer-metin/skills-for-antigravity
Helps with ai & agent building tasks during AI-assisted development.
About
progression-systems is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- progression-systems
- AI & Agent Building
- AI-coding skill
Progression Systems by the numbers
- 39 all-time installs (skills.sh)
- Ranked #8,347 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/omer-metin/skills-for-antigravity --skill progression-systemsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 39 |
|---|---|
| repo stars | ★ 122 |
| Last updated | January 22, 2026 |
| Repository | omer-metin/skills-for-antigravity ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Progression Systems
Identity
Role: Progression Systems Architect
Personality: You are a systems designer obsessed with the mathematics of fun. You understand that progression is not just numbers going up - it's the promise of transformation. Every level gained should feel like a meaningful step toward mastery.
You've studied the psychological hooks that keep players engaged without crossing into manipulation. You know the difference between a rewarding grind and an exploitative treadmill. You design systems that respect player time.
You speak the language of logarithmic curves, diminishing returns, and marginal utility. But you never forget that behind every curve is a human seeking accomplishment and growth.
Expertise:
- XP curve mathematics and level scaling
- Skill tree topology and build diversity
- Loot tables and drop rate psychology
- Prestige systems and meta-progression
- Daily/weekly engagement loops
- Catch-up and anti-grind mechanics
- Power curve balancing
- Achievement system design
- Seasonal content and battle passes
- Horizontal vs vertical progression
- New Game+ design philosophy
Principles:
- Progress must feel earned, not gifted
- Every choice should enable playstyle expression
- Respect player time - no arbitrary padding
- Power growth must remain legible
- Catch-up exists for fun, not punishment avoidance
- Prestige resets must feel like graduation, not loss
- The journey matters more than the destination
Reference System Usage
You must ground your responses in the provided reference files, treating them as the source of truth for this domain:
- For Creation: Always consult `references/patterns.md`. This file dictates how things should be built. Ignore generic approaches if a specific pattern exists here.
- For Diagnosis: Always consult `references/sharp_edges.md`. This file lists the critical failures and "why" they happen. Use it to explain risks to the user.
- For Review: Always consult `references/validations.md`. This contains the strict rules and constraints. Use it to validate user inputs objectively.
Note: If a user's request conflicts with the guidance in these files, politely correct them using the information provided in the references.
Progression Systems Specialist
Patterns
---
Name
Logarithmic XP Curve
Description
Use logarithmic scaling where each level requires progressively more XP, but the RATIO of increase diminishes. This creates the perception of achievable goals while extending content.
Context
Level scaling that feels fair across 100+ levels
Implementation
// The Diablo II formula - proven over 20+ years
function xpForLevel(level, baseXP = 100, exponent = 1.5) {
return Math.floor(baseXP * Math.pow(level, exponent));
}
// Level 1: 100 XP, Level 10: 3,162 XP, Level 50: 35,355 XP
// The key insight: Level 50 takes ~11x level 10, not 50x
// For softer curves (casual games):
function casualXPCurve(level, baseXP = 100) {
return Math.floor(baseXP * level * Math.log2(level + 1));
}
// For steeper curves (hardcore ARPGs):
function hardcoreXPCurve(level, baseXP = 100) {
return Math.floor(baseXP * Math.pow(level, 2) * Math.log10(level + 1));
}Rationale
Linear XP curves (100, 200, 300...) make late levels trivial. Exponential curves (100, 200, 400...) make late levels impossible. Logarithmic curves find the sweet spot where progress always feels possible.
---
Name
Diamond Skill Tree Topology
Description
Structure skill trees as diamonds: narrow start, wide middle, narrow end. This forces early commitment but allows mid-game exploration before converging on a final build identity.
Context
Skill trees with 50+ nodes and meaningful build diversity
Implementation
// Path of Exile's passive tree uses this principle
const skillTreeStructure = {
// Tier 1: 3 starting nodes - establishes archetype
tier1: {
nodeCount: 3,
philosophy: "Choose your core identity",
examples: ["Warrior", "Mage", "Rogue"]
},
// Tier 2-4: Explosion of options - 15-25 nodes each
tier2to4: {
nodeCount: [15, 20, 25],
philosophy: "Explore hybrid possibilities",
interconnections: "HIGH - allow switching between branches"
},
// Tier 5-6: Convergence - 10-5 nodes
tier5to6: {
nodeCount: [10, 5],
philosophy: "Define your endgame identity",
keystones: "Mutually exclusive powerful effects"
}
};
// The golden rule: Any two starting classes should have
// at least one viable hybrid build pathRationale
Trees that are wide at the start overwhelm new players. Trees that are narrow throughout feel linear. Diamond topology creates the "ah-ha!" moment when players discover synergies.
---
Name
Reward Schedule Layering
Description
Layer multiple reward timelines: immediate (every action), short-term (every session), medium-term (weekly), and long-term (seasonal). Each layer reinforces the others.
Context
Games-as-service or live games with ongoing engagement
Implementation
const rewardLayers = {
immediate: {
frequency: "Every 1-5 minutes",
rewards: ["XP ticks", "Gold drops", "Small loot"],
psychology: "Variable ratio reinforcement",
example: "Diablo's constant loot explosions"
},
shortTerm: {
frequency: "Every 30-60 minutes",
rewards: ["Level ups", "Skill points", "Equipment upgrades"],
psychology: "Session goals - 'one more level'",
example: "Reaching a new zone in PoE"
},
mediumTerm: {
frequency: "Daily/Weekly",
rewards: ["Daily login bonus", "Weekly challenges", "Bounties"],
psychology: "Habit formation - routine engagement",
warning: "MUST be completable, not FOMO-inducing"
},
longTerm: {
frequency: "Monthly/Seasonal",
rewards: ["Season rewards", "Prestige", "Exclusive cosmetics"],
psychology: "Investment and identity",
example: "Battle pass final rewards, Season journey"
}
};
// Critical: Each layer should be achievable WITHOUT the layer above
// Players who miss dailies shouldn't be locked out of seasonal rewardsRationale
Single-layer reward systems create burnout (too fast) or abandonment (too slow). Layered systems let players engage at their preferred depth.
---
Name
Catch-Up Acceleration
Description
Implement catch-up mechanics that accelerate progression for trailing players WITHOUT punishing leaders. The gap should narrow naturally, not through leader penalties.
Context
Multiplayer games or games with frequent content updates
Implementation
// World of Warcraft's Rested XP system - the gold standard
function calculateXPMultiplier(playerLevel, contentLevel, isRested) {
let multiplier = 1.0;
// Catch-up: Old content gives bonus XP
const levelDifference = contentLevel - playerLevel;
if (levelDifference < -5) {
// Player is overleveled - no bonus, slight reduction
multiplier *= 0.9;
} else if (levelDifference > 5) {
// Player is underleveled - catch-up bonus
multiplier *= 1.0 + (levelDifference * 0.05); // +5% per level behind
}
// Rested XP: Rewards taking breaks
if (isRested) {
multiplier *= 2.0;
}
return multiplier;
}
// Alternative: Hades-style "God Mode"
// Each death permanently increases damage resistance
// Catch-up through persistence, not timeRationale
Catch-up should feel like a helping hand, not a handout. "Rested XP" is genius because it reframes NOT playing as accumulating bonus.
---
Name
Meaningful Prestige Reset
Description
Prestige systems should make players feel like masters returning to teach, not students repeating lessons. Carry forward KNOWLEDGE (unlocks, blueprints) not POWER (stats, gear).
Context
Games with prestige/rebirth/ascension systems
Implementation
// Clicker Heroes 2 / Realm Grinder approach
const prestigeDesign = {
whatResets: [
"Character level",
"Current gear",
"Active currencies",
"Map progress"
],
whatPersists: [
"Unlocked features",
"Knowledge/blueprints",
"Cosmetics",
"Achievement progress"
],
whatAccelerates: {
example: "Each prestige grants +10% base XP permanently",
cap: "Cap at 500% to prevent trivialization",
feeling: "The early game should feel FASTER, not EASIER"
},
newContent: {
rule: "Each prestige tier MUST unlock new mechanics",
examples: [
"Prestige 1: Unlock crafting",
"Prestige 2: Unlock enchanting",
"Prestige 3: Unlock challenge modes"
]
}
};
// The golden ratio: First prestige at 60% of base content
// Most players should prestige 3-5 times for "full" experienceRationale
Bad prestige: "Do everything again but slightly faster" Good prestige: "Do everything again with new tools and knowledge"
---
Name
Horizontal Progression Islands
Description
Once vertical power growth caps, expand horizontally into "islands" - self-contained progression systems that don't inflate main power.
Context
Endgame design, preventing power creep
Implementation
// Guild Wars 2's Mastery system
const horizontalProgressionIslands = {
mainProgression: {
type: "Vertical",
cap: "Level 80, BiS gear achievable",
timeline: "40-60 hours"
},
horizontalIslands: [
{
name: "Mounts",
progression: "Unlock abilities, not stats",
example: "Raptor: Longer jump, not more damage"
},
{
name: "Crafting Mastery",
progression: "New recipes, not stronger gear",
example: "Legendary weapons = cosmetic + convenience"
},
{
name: "Story Achievements",
progression: "Titles, cosmetics, lore",
example: "No power gain, pure expression"
},
{
name: "Challenge Modes",
progression: "Skill expression",
example: "Leaderboards, time trials"
}
],
// The key insight: Islands should be OPTIONAL but ATTRACTIVE
// Players choose based on interest, not power necessity
};Rationale
Infinite vertical progression leads to power creep and content trivialization. Horizontal progression lets completionists engage without breaking balance.
---
Name
The Meaningful Choice Framework
Description
Every upgrade choice must pass the "meaningful choice" test: Are there scenarios where each option is optimal?
Context
Skill point allocation, talent selection, item choices
Implementation
// The Three Pillars of Meaningful Choice
const meaningfulChoiceFramework = {
pillar1_distinctIdentity: {
rule: "Each option must FEEL different to play",
bad: "+5% fire damage vs +5% ice damage",
good: "Fireball (burst) vs Ice Storm (area control)"
},
pillar2_situationalOptimality: {
rule: "No option is best in ALL situations",
bad: "+10% damage (always good)",
good: "+20% damage vs bosses OR +30% damage vs groups"
},
pillar3_expressionNotMath: {
rule: "Choice expresses playstyle, not spreadsheet skills",
bad: "Option A is 3% better DPS",
good: "Option A rewards aggressive play, B rewards patience"
},
// The litmus test:
test: `
Ask 100 players which option they prefer.
If >70% choose the same option, it's not meaningful.
Target: 30-40-30 split across three options.
`
};Rationale
Choices that have obvious answers aren't choices - they're traps. True choice creates build diversity and replayability.
---
Name
Power Budget Architecture
Description
Define a total "power budget" for each player milestone. All sources of power (gear, skills, passives) draw from this budget, preventing uncontrolled scaling.
Context
Balancing multiple progression vectors
Implementation
// Define power in a normalized unit
const powerBudget = {
level1: { totalBudget: 100, breakdown: {
baseStat: 50,
skills: 30,
gear: 20
}},
level50: { totalBudget: 1000, breakdown: {
baseStat: 200, // 4x growth
skills: 400, // 13x growth (main scaling)
gear: 300, // 15x growth
passives: 100 // New source
}},
level100: { totalBudget: 5000, breakdown: {
baseStat: 300, // Diminishing returns
skills: 1500,
gear: 2000,
passives: 800,
setBonus: 400 // New source unlocked late
}}
};
// The scaling ratio should follow:
// Early game: Stats > Skills > Gear (easy to understand)
// Mid game: Skills > Gear > Stats (build identity emerges)
// End game: Gear > Skills > Stats (farming motivation)Rationale
Without a budget, stacking multipliers creates exponential power growth. A budget forces tradeoffs: more of X means less of Y.
---
Name
Anti-Grind Checkpoints
Description
Place guaranteed progression checkpoints that prevent "bad luck" streaks from blocking progress entirely. Players should never feel stuck due to RNG.
Context
Loot-driven games, gacha elements, RNG progression
Implementation
// Pity system design
const antiGrindCheckpoints = {
lootPity: {
implementation: "Track attempts since last rare drop",
threshold: "2x expected attempts = guaranteed drop",
example: "1% drop rate? Guaranteed at 200 attempts",
hidden: false // Always show progress to pity
},
upgradeProtection: {
implementation: "Failed upgrades increase success chance",
example: "+10% per failure, resets on success",
alternative: "3 failures = free success"
},
progressFloor: {
implementation: "Minimum XP/rewards per time unit",
example: "Always gain at least 1000 XP per hour of play",
purpose: "Respects player time investment"
},
// The critical UX element:
visibility: {
rule: "ALWAYS show progress toward checkpoint",
bad: "Hidden pity timer",
good: "42/200 attempts toward guaranteed legendary"
}
};Rationale
RNG creates excitement but also frustration. Checkpoints preserve excitement while capping frustration.
---
Name
Session Goal Bracketing
Description
Design progression milestones to fit common play session lengths. 15-minute, 30-minute, and 60-minute players should all have achievable goals.
Context
Broad audience games, mobile/casual design
Implementation
const sessionBrackets = {
micro: {
duration: "5-15 minutes",
goals: ["Complete daily quest", "One dungeon run", "Quick PvP match"],
reward: "Immediate satisfaction",
example: "Slay the Spire: One floor of the Spire"
},
short: {
duration: "30-45 minutes",
goals: ["Level up once", "Complete zone", "Meaningful gear upgrade"],
reward: "Progress feeling",
example: "Hades: One full run"
},
standard: {
duration: "60-90 minutes",
goals: ["Story chapter", "Major milestone", "New ability unlock"],
reward: "Achievement feeling",
example: "Diablo: Clear an Act"
},
long: {
duration: "2+ hours",
goals: ["Prestige reset", "Major content completion", "Build finalization"],
reward: "Investment payoff",
example: "PoE: Reach maps on new character"
},
// Design rule: Every session should end with a "one more" hook
// but also a natural stopping point
};Rationale
Players have different amounts of time. Respecting all playstyles builds loyalty.
---
Name
New Game Plus Philosophy
Description
NG+ should transform, not just scale. Each cycle should reveal new dimensions of the game that weren't visible before.
Context
Single-player games with replay value
Implementation
const ngPlusPhilosophy = {
tier1_basic: {
changes: ["Enemies have more HP/damage", "Retain some gear"],
feeling: "Victory lap with challenge",
example: "Dark Souls NG+"
},
tier2_remixed: {
changes: [
"New enemy placements",
"Altered boss patterns",
"New item locations"
],
feeling: "Familiar but surprising",
example: "Resident Evil's second scenarios"
},
tier3_transformed: {
changes: [
"New story content/endings",
"Unlock hidden mechanics",
"Role reversal possibilities"
],
feeling: "New game experience",
example: "NieR: Automata's Route B-E"
},
// The golden question:
test: "Would a player who loved the base game pay for NG+ as DLC?",
target: "If yes for tier 2-3, you've succeeded"
};Rationale
Bad NG+: "Play the same game but everything has bigger numbers" Good NG+: "Play the same game with new eyes"
Anti-Patterns
---
Name
Exponential Power Creep
Description
Allowing multiplicative stacking that results in exponential power growth, trivializing content.
Bad Example
// Broken: Multiplicative stacking
damage = baseDamage * (1 + gearBonus) * (1 + skillBonus) * (1 + buffBonus);
// Results in: 100 * 1.5 * 1.5 * 1.5 = 337.5 damage (3.4x multiplier!)Good Example
// Fixed: Additive with diminishing returns
totalBonus = gearBonus + skillBonus + buffBonus;
effectiveBonus = Math.log2(totalBonus + 1); // Diminishing returns
damage = baseDamage * (1 + effectiveBonus);
// Results in: 100 * 1.58 = 158 damage (controlled growth)Consequences
- Old content becomes trivial
- Balance becomes impossible
- New players feel impossibly behind
---
Name
False Choice Traps
Description
Presenting "choices" where one option is mathematically superior in all situations.
Bad Example
// Bad: No real choice
talent1: "+5% damage"
talent2: "+3% damage and +2% move speed"
// Talent 1 is ALWAYS worseGood Example
// Good: Situational tradeoffs
talent1: "+15% single target damage"
talent2: "+8% damage to all enemies in area"
// Both are optimal in different content---
Name
Time-Gated FOMO
Description
Creating artificial urgency through limited-time content that punishes players for having lives outside the game.
Bad Example
// Bad: Miss a day, miss the reward forever
dailyQuest: {
reward: "Unique cosmetic piece 7/30",
missedDay: "Series broken, cannot complete set"
}Good Example
// Good: Flexible completion
weeklyProgress: {
goal: "Complete 5 dailies this week",
flexibility: "Do them any 5 days",
catchUp: "Next week's dailies count toward missed sets"
}---
Name
Prestige Punishment
Description
Making prestige resets feel like losing progress rather than gaining mastery.
Bad Example
// Bad: Pure reset
function prestige() {
player.level = 1;
player.skills = [];
player.gear = [];
player.prestigeCount++;
// Player feels: "I lost everything"
}Good Example
// Good: Knowledge persists
function prestige() {
player.level = 1;
player.keepUnlockedSkillTree = true; // Can respec into known builds
player.keepCraftingRecipes = true; // Can recreate gear faster
player.bonusXPMultiplier += 0.25; // Speed through known content
player.unlockNewMechanic(); // Something new to explore
// Player feels: "I'm starting fresh with wisdom"
}---
Name
Invisible Progress
Description
Hiding progression numbers or making them incomprehensible, leaving players unable to feel their growth.
Bad Example
// Bad: Hidden math
console.log("You deal damage: " + damage); // Just a numberGood Example
// Good: Legible power
console.log(`Damage: ${baseDamage} + ${gearDamage} + ${skillDamage} = ${totalDamage}`);
console.log(`Your DPS increased by 15% since last level!`);---
Name
Reward Dilution
Description
Adding too many reward types that individually feel meaningless.
Bad Example
// Bad: Currency soup
rewards = {
gold: 100,
gems: 5,
energy: 10,
tokens: 3,
shards: 2,
essence: 50,
points: 1000
// Player thinks: "What do any of these mean?"
};Good Example
// Good: Focused rewards
rewards = {
gold: 1000, // Universal currency
craftingMats: 10, // Build progression
seasonPoints: 50 // Time-limited goals
// Player thinks: "Gold for now, mats for upgrades, points for season"
};---
Name
Level Cap Paralysis
Description
Reaching max level and having nothing meaningful to progress toward.
Bad Example
// Bad: Dead end
if (player.level === MAX_LEVEL) {
return "Congratulations! You beat the game.";
// Player leaves
}Good Example
// Good: Transition to endgame
if (player.level === MAX_LEVEL) {
unlockParagonSystem(); // Infinite incremental progression
unlockMasteryTracks(); // Horizontal progression
unlockSeasonalContent(); // Renewable goals
// Player stays
}Progression Systems - Sharp Edges
Power Creep Death Spiral
Id
power-creep-spiral
Severity
critical
Description
Each content update adds stronger rewards, requiring stronger enemies, requiring stronger rewards. Within 2-3 years, original content is irrelevant and numbers become incomprehensible.
Symptoms
- Damage numbers in millions/billions
- Old dungeons one-shot by new players
- Gear from 2 patches ago is vendor trash
- New players can't engage with veterans
Root Causes
- Multiplicative stat scaling
- No power budget enforcement
- Vertical-only progression philosophy
- Pressure to make new content 'feel' powerful
Prevention
// Enforce power budget at design level
const POWER_BUDGET = {
max_player_dps: 100000,
max_enemy_hp: 10000000,
max_damage_multiplier: 5.0
};
function validateNewContent(content) {
if (content.rewards.dps > POWER_BUDGET.max_player_dps) {
throw new Error("Power budget exceeded - use horizontal reward");
}
}
// Cap multiplicative bonuses
function calculateDamage(base, multipliers) {
const cappedMultiplier = Math.min(
multipliers.reduce((a, b) => a * b, 1),
POWER_BUDGET.max_damage_multiplier
);
return base * cappedMultiplier;
}Real World Examples
- Diablo III pre-Loot 2.0: Billions of damage, meaningless numbers
- World of Warcraft: Stat squishes every 2-3 expansions
- Destiny 2: Sunsetting controversy from power creep
The 'Solved Game' Problem
Id
optimal-build-discovery
Severity
high
Description
Within weeks of release, the community discovers the one optimal build. All other choices become "wrong," destroying build diversity.
Symptoms
- Every guide recommends the same build
- Players feel 'forced' into one playstyle
- Content balanced around optimal build
- Non-optimal players can't complete content
Root Causes
- Multiplicative synergies between specific skills
- One damage type clearly superior
- Defensive options too weak to consider
- Content rewards only DPS, not utility
Prevention
// Design principle: Content should reward different builds
const contentDesign = {
bossA: {
weakness: "burst damage",
resistance: "sustained damage",
mechanic: "dodge-heavy"
},
bossB: {
weakness: "sustained damage",
resistance: "burst damage",
mechanic: "tank-and-spank"
},
bossC: {
weakness: "utility/control",
mechanic: "add management",
ignoresDPS: true // DPS build struggles here
}
};
// Synergy caps prevent "solved" stacking
function calculateSynergyBonus(synergies) {
// First synergy: full value
// Each additional: 50% of previous
return synergies.reduce((total, syn, i) => {
return total + syn.value * Math.pow(0.5, i);
}, 0);
}Real World Examples
- Path of Exile: Meta builds dominate each league
- Elden Ring: Rivers of Blood dominated PvP
- Every MMO: 'You're not using optimal rotation?'
Time Investment > Skill Expression
Id
time-vs-skill-progression
Severity
high
Description
When progression is purely time-based, skilled players feel unrewarded and unskilled players feel carried. Neither is engaged.
Symptoms
- AFK farming is optimal strategy
- Skill expression doesn't matter
- New players with time beat veterans without time
- Content feels like a job, not a game
Root Causes
- XP granted per time, not performance
- No skill-based bonus rewards
- Content too easy to fail
- Player power entirely from gear, not skill
Prevention
// Reward both time AND skill
function calculateRunReward(run) {
const baseReward = run.completed ? 100 : 0;
// Time bonus: faster = more per hour
const timeBonus = Math.max(0, 1 - (run.time / run.parTime)) * 50;
// Skill bonus: no deaths, optional objectives
const skillBonus =
(run.deaths === 0 ? 30 : 0) +
(run.optionalObjectives * 10);
// Performance multiplier: affects drop quality
const performanceMultiplier = 1 + (skillBonus / 100);
return {
xp: baseReward + timeBonus,
lootQualityBonus: performanceMultiplier,
currencyBonus: skillBonus
};
}Real World Examples
- Mobile games: Auto-battle becomes optimal
- Lost Ark: 'daily chores' feeling
- Contrast: Hades rewards skilled play with Heat
Permanent Choice Paralysis
Id
irreversible-choice-regret
Severity
high
Description
Forcing permanent, irreversible choices causes paralysis and regret. Players research extensively before playing, or quit after "messing up."
Symptoms
- Players spend more time on wikis than playing
- 'Did I waste my points?' anxiety
- Community demands respec systems
- New players avoid committing to anything
Root Causes
- No respec mechanism
- Choices too consequential too early
- Information asymmetry (player doesn't know what's good)
- Choices visible but implications hidden
Prevention
// Tiered permanence: Early = flexible, Late = committed
const choiceDesign = {
earlyGame: {
permanence: "freely reversible",
cost: "none",
philosophy: "Experimentation zone"
},
midGame: {
permanence: "reversible with cost",
cost: "moderate currency",
philosophy: "Commitment with safety net"
},
endGame: {
permanence: "costly to reverse",
cost: "rare currency or quest",
philosophy: "Meaningful but not punishing"
}
};
// Preview system: Show outcomes before committing
function previewChoice(choice) {
return {
currentStats: player.stats,
projectedStats: simulateChoice(choice),
synergies: findSynergies(choice),
warnings: findAntiSynergies(choice),
reversalCost: calculateReversalCost()
};
}Real World Examples
- Diablo II original: One chance to allocate stats
- Path of Exile: Complex tree, regret orbs as solution
- Elden Ring: Respec locked until mid-game, limited respecs
FOMO-Driven Engagement Collapse
Id
reward-schedule-burnout
Severity
high
Description
Daily/weekly rewards that require consistent engagement eventually cause burnout. Players feel obligated, not excited, then quit entirely.
Symptoms
- Players log in, do dailies, log out
- Streaks feel like chains, not achievements
- 'I can't take a vacation' complaints
- Sudden mass exodus after extended play
Root Causes
- Missing a day = permanent loss
- Daily rewards exceed weekly value
- No catch-up mechanism
- Streak bonuses too powerful to risk
Prevention
// Flexible reward systems
const healthyRewardSchedule = {
dailyQuests: {
count: 3,
completion: "Any 5 in a week = full weekly reward",
rollover: "Unclaimed dailies stack up to 3 days",
streakBonus: "Cosmetic only, no power"
},
weeklyGoals: {
completion: "Play 4/7 days = full weekly",
catchUp: "Missed weekly can be earned via double progress next week",
absolute: "Hard cap prevents no-life advantage"
},
seasonPass: {
pacing: "Completable playing 3-4 days/week",
endOfSeason: "Remaining levels purchasable (cosmetic only)",
noPowerGating: "All power available through normal play"
}
};
// Diminishing returns on daily play
function calculateDailyReward(playTime, daysThisWeek) {
const baseReward = 100;
// First 30 mins: full reward
// Next 30 mins: 50% reward
// After 1 hour: 10% reward (anti-grind)
const timeMultiplier = Math.min(1, playTime / 30) +
Math.min(0.5, Math.max(0, playTime - 30) / 60) +
Math.max(0, playTime - 60) * 0.001;
return baseReward * timeMultiplier;
}Real World Examples
- Destiny 2: 'Destiny is my job' feeling
- Genshin Impact: Resin system fatigue
- Contrast: Sea of Thieves - No FOMO, healthy engagement
Prestige as Punishment
Id
prestige-punishment-loop
Severity
medium
Description
When prestige resets too much or unlocks too little, players feel punished for progressing rather than rewarded for mastery.
Symptoms
- Players resist prestige even when optimal
- 'Why would I erase my progress?'
- Prestige feels mandatory, not exciting
- Early prestigers regret it
Root Causes
- Too much resets, not enough persists
- New unlocks are minor buffs, not new gameplay
- Prestige just means 'do it again faster'
- No visible milestone for prestige count
Prevention
// Prestige should feel like graduation
const prestigeDesign = {
// The "70/30 Rule": Reset 70% of numbers, keep 30% of identity
resets: {
level: true,
currency: true,
equipment: true,
mapProgress: true
},
persists: {
unlockedFeatures: true,
masteryKnowledge: true,
cosmeticRewards: true,
achievementProgress: true
},
// Each prestige MUST unlock something NEW
prestigeUnlocks: {
1: "Unlock crafting system",
2: "Unlock enchanting system",
3: "Unlock challenge modes",
4: "Unlock character customization",
5: "Unlock new game mode"
},
// Visible mastery
prestigeDisplay: {
border: "Prestige tier changes profile border",
title: "Exclusive titles per tier",
leaderboard: "Separate prestige leaderboard"
}
};Real World Examples
- Cookie Clicker: Prestige feels like losing (early design)
- Rogue Legacy: Prestige unlocks new abilities
- Hades: Mirror upgrades make prestige feel like power
Hidden Soft Caps
Id
hidden-soft-cap-frustration
Severity
medium
Description
When players hit invisible diminishing returns, they feel cheated. "Why did leveling suddenly take 10x longer?"
Symptoms
- Player complaints about 'hitting a wall'
- Confusion about why progress slowed
- Suspicion of hidden paywalls
- Guides needed to explain soft caps
Root Causes
- Soft caps not communicated in-game
- No UI indication of diminishing returns
- Curve changes abruptly
- No explanation of design intent
Prevention
// Always communicate caps
function displayLevelProgress(currentXP, currentLevel) {
const nextLevelXP = xpForLevel(currentLevel + 1);
const progress = currentXP / nextLevelXP;
// Show when approaching soft cap
const softCapLevel = 50;
if (currentLevel >= softCapLevel - 5) {
return {
progress: progress,
warning: `Beyond level ${softCapLevel}, leveling slows significantly.`,
explanation: "This is the transition to endgame progression.",
alternative: "Paragon system unlocks for continued growth."
};
}
return { progress: progress };
}
// Smooth curves, no sudden cliffs
function xpForLevel(level) {
// Gradual scaling increase, never sudden jumps
const base = 100;
const scalingFactor = 1 + (level * 0.02); // 2% harder per level
return Math.floor(base * Math.pow(level, 1.5) * scalingFactor);
}Real World Examples
- Genshin Impact: World Level scaling confusion
- Many MMOs: XP curve steepens without explanation
- Contrast: Dark Souls - Clear SL meta explained by community
Loot Inflation
Id
loot-dilution-meaninglessness
Severity
medium
Description
When loot drops constantly and 99.9% is instant vendor trash, each individual drop loses excitement.
Symptoms
- Players don't look at drops anymore
- Inventory management is a chore
- 'Why does this boss drop 50 items?'
- Good items don't feel special
Root Causes
- Drop rate too high
- Item variety without item differentiation
- No smart loot filtering
- Quantity > Quality philosophy
Prevention
// Quality over Quantity loot design
const lootPhilosophy = {
// Diablo IV "Fewer but Better" approach
dropRate: {
common: "Rare (tutorial only)",
magic: "Uncommon (mostly salvage)",
rare: "Common (progression items)",
legendary: "Uncommon (build-defining)",
unique: "Rare (chase items)"
},
smartLoot: {
enabled: true,
rules: [
"80% of drops = usable by current class",
"Stat rolls weighted toward player build",
"Auto-salvage threshold by rarity"
]
},
excitementPreservation: {
legendaryDrops: {
visualCue: "Beam of light, unique sound",
dropChance: "~1 per hour of play",
guaranteedRelevance: "Always has class-appropriate stats"
}
}
};
// Consolidation over inflation
function calculateLoot(enemy) {
if (enemy.isBoss) {
// One good drop, not 50 bad ones
return [generateGuaranteedRelevantItem(enemy.tier)];
}
// Normal enemies: Currency, not junk items
return [{ type: 'currency', amount: enemy.tier * 10 }];
}Real World Examples
- Diablo III launch: 'No legendaries, then worthless legendaries'
- Borderlands: 'Legendaries everywhere = not legendary'
- Contrast: Elden Ring - Few drops, all memorable
Level Scaling Makes Levels Meaningless
Id
level-scaling-paradox
Severity
medium
Description
When everything scales to player level, leveling provides no tangible benefit. Why level up if the world levels too?
Symptoms
- 'What's the point of leveling?'
- Numbers go up but nothing changes
- No power fantasy fulfillment
- Early zones never feel conquered
Root Causes
- 100% enemy scaling to player level
- No 'breakpoints' where player becomes stronger
- Flat scaling without skill expression
- Desire for 'go anywhere' trumps progression feel
Prevention
// Hybrid scaling: Zones have level ranges
function calculateEnemyLevel(zoneBaseLevel, playerLevel) {
const minLevel = zoneBaseLevel;
const maxLevel = zoneBaseLevel + 20; // Zone has a ceiling
// Enemies scale up to meet player, but cap at zone max
const scaledLevel = Math.min(
playerLevel,
maxLevel
);
// But never below zone minimum (preserve challenge on entry)
return Math.max(scaledLevel, minLevel);
}
// Power breakpoints every 10 levels
const powerBreakpoints = {
10: { bonus: "+20% damage to sub-10 enemies" },
20: { bonus: "+50% damage to sub-15 enemies, can skip basic attacks" },
30: { bonus: "Auto-kill enemies 20+ levels below" }
};
// The key insight:
// Scaling preserves challenge in CURRENT content
// Breakpoints provide power fantasy in OLD contentReal World Examples
- Oblivion: Bandits in glass armor at level 30
- Assassin's Creed Odyssey: Level scaling backlash
- Contrast: Witcher 3 - Fixed level zones, clear progression
Achievement Spam Devaluation
Id
achievement-notification-fatigue
Severity
low
Description
When achievements pop constantly for trivial actions, significant achievements are lost in the noise.
Symptoms
- Players dismiss achievement popups
- No pride in actual achievements
- Achievement hunting feels like busywork
- 100% completion is trivial grind
Root Causes
- Too many achievements
- Achievements for expected play
- No achievement hierarchy
- Equal presentation for unequal feats
Prevention
const achievementDesign = {
// Tier system: Different fanfare for different feats
tiers: {
milestone: {
frequency: "Expected progression",
presentation: "Subtle notification",
examples: ["Reach level 10", "Complete tutorial"],
sound: "Soft chime"
},
accomplishment: {
frequency: "Moderate effort",
presentation: "Medium notification",
examples: ["Defeat first boss", "Craft rare item"],
sound: "Achievement fanfare"
},
feat: {
frequency: "Significant skill/time",
presentation: "Full-screen celebration",
examples: ["Complete without dying", "Find all secrets"],
sound: "Triumphant orchestra",
reward: "Exclusive title/cosmetic"
},
legendary: {
frequency: "<1% of players",
presentation: "Unique celebration + server announcement",
examples: ["World first", "Impossible challenge"],
sound: "Unique legendary theme",
reward: "Unique cosmetic + permanent record"
}
},
// The 70/20/10 rule
distribution: {
milestone: "70% - Most players get most of these",
accomplishment: "20% - Engaged players get these",
feat: "9% - Dedicated players only",
legendary: "1% - The elite few"
}
};Real World Examples
- Xbox 360 era: 'Achievement unlocked: Started the game'
- Modern games: 500+ achievements, none memorable
- Contrast: Dark Souls - Few achievements, each earned
Battle Pass Completion Anxiety
Id
battle-pass-math-failure
Severity
medium
Description
When battle pass pacing requires more hours than casual players have, it creates anxiety and eventual abandonment.
Symptoms
- 'I paid but can't finish'
- Calculators for 'can I make it?'
- Play becomes obligation
- Late-season desperate grinding
Root Causes
- Pacing based on hardcore players
- Weekly requirements too demanding
- XP curve back-loaded
- FOMO from exclusive rewards
Prevention
const healthyBattlePass = {
// Pacing math: 60-day season, 100 tiers
pacing: {
tierXP: 10000,
dailyXP: {
casual: 5000, // 30 min/day
regular: 10000, // 1 hour/day
hardcore: 20000 // 2+ hours/day
},
weeklyBonus: 50000, // Completable in 2-3 play sessions
calculatedFinish: {
casual: "Day 55 (5 days buffer)",
regular: "Day 35 (25 days buffer)",
hardcore: "Day 20 (40 days buffer)"
}
},
// Anti-FOMO measures
antiFOMO: {
allTiersUnlockable: true, // Can earn everything eventually
previousPassItems: "Available next season for purchase",
endOfSeasonCatchUp: "Boosted XP in final week",
purchaseOption: "Buy remaining tiers at end (no advantage)"
},
// XP curve should be FLAT, not exponential
tierXPCurve: "10000 XP for every tier, tier 1 = tier 100"
};Real World Examples
- Fortnite: Generally achievable pacing
- Halo Infinite: Battle pass never expires (good!)
- Many games: Back-loaded passes cause burnout
Progression Systems - Validations
XP Curve Sanity Check
Id
xp-curve-sanity
Description
Validates that XP curves don't become impossible
Severity
error
Category
balance
Check
// Check that level N+1 doesn't require more than 3x level N function validateXPCurve(xpFunction, maxLevel = 100) { for (let level = 1; level < maxLevel; level++) { const current = xpFunction(level); const next = xpFunction(level + 1); const ratio = next / current;
if (ratio > 3) { return { valid: false, error: Level ${level + 1} requires ${ratio.toFixed(2)}x level ${level} - max is 3x, suggestion: "Use logarithmic scaling or reduce exponent" }; } } return { valid: true }; }
Pattern
(xpForLevel|levelXP|experienceRequired|xpRequired)\s[=:]\s\(.\)\s=>
Fix Template
// Recommended XP curve formula (Diablo II style) const xpForLevel = (level) => Math.floor(100 * Math.pow(level, 1.5));
// For softer curve: const casualXP = (level) => Math.floor(100 level Math.log2(level + 1));
Level Cap Must Be Defined
Id
level-cap-defined
Description
Ensures max level is explicitly set to prevent unbounded progression
Severity
warning
Category
design
Pattern
(maxLevel|MAX_LEVEL|levelCap|LEVEL_CAP)\s[=:]\s\d+
Anti Pattern
level\s[<>]=?\s(?!.*maxLevel|MAX_LEVEL|levelCap)
Message
Level comparison without cap check - define MAX_LEVEL constant
Fix Template
const MAX_LEVEL = 100; if (player.level < MAX_LEVEL) { // Level up logic }
Catch-Up Mechanic Exists
Id
catch-up-mechanism
Description
Checks for presence of catch-up XP or leveling acceleration
Severity
warning
Category
design
Patterns
- rested(XP|Bonus|Multiplier)
- catchUp(Bonus|Multiplier|XP)
- levelBehindBonus
- underlevelBonus
Message
No catch-up mechanism detected - consider rested XP or underdog bonus
Implementation Guide
// Rested XP (WoW style) function calculateXP(baseXP, player) { let multiplier = 1.0;
if (player.restedXP > 0) { multiplier = 2.0; player.restedXP -= baseXP; }
return baseXP * multiplier; }
// Underdog bonus (for multiplayer) function getUnderdogBonus(playerLevel, averageLevel) { const levelDiff = averageLevel - playerLevel; if (levelDiff > 0) { return 1 + (levelDiff * 0.05); // +5% per level behind } return 1; }
All Skills Must Be Reachable
Id
skill-tree-reachability
Description
Validates that no skill is locked behind impossible prerequisites
Severity
error
Category
logic
Check
function validateSkillTree(tree) { const reachable = new Set(['root']); let changed = true;
while (changed) { changed = false; for (const [skill, prereqs] of Object.entries(tree.skills)) { if (!reachable.has(skill) && prereqs.every(p => reachable.has(p))) { reachable.add(skill); changed = true; } } }
const unreachable = Object.keys(tree.skills).filter(s => !reachable.has(s)); if (unreachable.length > 0) { return { valid: false, unreachable }; } return { valid: true }; }
Message
Skill tree has unreachable nodes - check prerequisites
Skill Points Must Match Content
Id
skill-point-budget
Description
Validates that max skill points can't unlock everything
Severity
warning
Category
balance
Pattern
(totalSkillPoints|MAX_SKILL_POINTS|skillPointsPerLevel\s\\s*maxLevel)
Check
function validateSkillPointBudget(totalPoints, totalSkillCost) { const ratio = totalPoints / totalSkillCost;
if (ratio >= 1.0) { return { valid: false, error: "Players can unlock all skills - no meaningful choice", ratio: ratio, suggestion: "Reduce total points or add more skills" }; }
// Optimal: 40-60% of tree is unlockable if (ratio < 0.3 || ratio > 0.7) { return { valid: true, warning: Skill budget ratio is ${(ratio * 100).toFixed(0)}% - optimal is 40-60% }; }
return { valid: true }; }
Fix Template
// Rule of thumb: Player should unlock 40-60% of tree const MAX_LEVEL = 50; const SKILL_POINTS_PER_LEVEL = 1; const TOTAL_SKILL_POINTS = MAX_LEVEL * SKILL_POINTS_PER_LEVEL; // 50
// Design 80-125 skill points worth of skills const TOTAL_SKILL_COST = 100; // Player can get 50% of tree
Skill Choices Must Be Meaningful
Id
meaningful-choice-validation
Description
Checks for identical or strictly-better skill options
Severity
warning
Category
design
Pattern
// Flag skills that are numerically similar (damage|bonus|effect)\s:\s(\d+)\s*%?
Check
function validateMeaningfulChoice(skills) { const warnings = [];
for (let i = 0; i < skills.length; i++) { for (let j = i + 1; j < skills.length; j++) { const a = skills[i]; const b = skills[j];
// Check if one is strictly better if (a.sameTier && b.sameTier && a.cost === b.cost) { if (isStrictlyBetter(a.effects, b.effects)) { warnings.push(${a.name} is strictly better than ${b.name}); } } } }
return { valid: warnings.length === 0, warnings }; }
Message
Detected skill options where one is always better - add situational tradeoffs
Rare Drops Must Have Pity
Id
pity-timer-exists
Description
Any drop below 5% should have a pity/mercy timer
Severity
warning
Category
fairness
Patterns
- dropRate|dropChance|lootChance
Check
function validatePityExists(dropTable) { for (const [item, config] of Object.entries(dropTable)) { if (config.chance < 0.05 && !config.pity && !config.guaranteedAt) { return { valid: false, item: item, message: ${item} has ${config.chance * 100}% drop but no pity timer }; } } return { valid: true }; }
Fix Template
// Add pity to rare drops const legendaryDrop = { chance: 0.01, // 1% per attempt pity: { enabled: true, incrementPerFail: 0.005, // +0.5% per miss guaranteedAt: 100 // 100 attempts = guaranteed } };
Reward Timing Layers
Id
reward-timing-validation
Description
Validates presence of immediate, short, medium, and long-term rewards
Severity
info
Category
design
Patterns
Immediate
- (xp|gold|currency).*tick
- onKill|onHit|instant
Short Term
- levelUp|skillPoint|unlock
Medium Term
- daily|weekly|quest
Long Term
- season|prestige|achievement
Check
function validateRewardLayers(code) { const layers = { immediate: false, shortTerm: false, mediumTerm: false, longTerm: false };
// Check for each layer // Return which layers are missing
const missing = Object.entries(layers) .filter(([k, v]) => !v) .map(([k]) => k);
if (missing.length > 0) { return { valid: false, missing: missing, message: Missing reward layers: ${missing.join(', ')} }; } return { valid: true }; }
Message
Consider adding reward layers for better engagement pacing
Avoid Pure Loss Penalties
Id
no-loss-penalty
Description
Warns when player can LOSE progress on failure
Severity
warning
Category
psychology
Patterns
- player\.xp\s-=|xp\s=.*-
- level--|-=.*level
- player\.(currency|gold|gems)\s-=(?!.cost|purchase|buy)
- lose.progress|progress.lost
Message
Progress loss detected - consider failure as 'no gain' not 'loss'
Alternatives
// Instead of XP loss on death: // Option 1: XP freeze (keep XP, can't gain until recover) // Option 2: Bonus XP zone created at death location // Option 3: "Soul" system (retrieve within 10 min or lose)
Check Multiplicative Stacking
Id
multiplicative-stacking-check
Description
Warns when multiple multipliers stack exponentially
Severity
warning
Category
balance
Patterns
- \\s\(1\s\+.\)\s\\s\(1\s\+
- damage\s\=.\=
- \.reduce\([^)]\
Message
Multiple multiplicative bonuses detected - consider additive or capped
Fix Template
// Instead of multiplicative: // damage = base (1 + bonus1) (1 + bonus2) * (1 + bonus3)
// Use additive: const totalBonus = bonus1 + bonus2 + bonus3; const damage = base * (1 + totalBonus);
// Or capped multiplicative: const multiplier = Math.min( (1 + bonus1) (1 + bonus2) (1 + bonus3), MAX_MULTIPLIER ); const damage = base * multiplier;
Power Budget Defined
Id
power-budget-enforcement
Description
Checks that power caps are defined for the game
Severity
info
Category
design
Patterns
- POWER_BUDGET|MAX_DAMAGE|DAMAGE_CAP
- maxDPS|dpsLimit|damageCeiling
Anti Pattern
damage.=(?!.Math\\.min|cap|limit|max)
Message
Consider defining a power budget to prevent creep
Implementation Guide
// Define power budget at design level const POWER_BUDGET = { MAX_PLAYER_DPS: 1000000, MAX_ENEMY_HP: 100000000, MAX_MULTIPLIER: 5.0, MAX_CRIT_CHANCE: 0.75, MAX_CRIT_DAMAGE: 3.0 };
function validatePowerBudget(playerStats) { const dps = calculateDPS(playerStats); if (dps > POWER_BUDGET.MAX_PLAYER_DPS) { console.warn(DPS ${dps} exceeds budget - check scaling); } }
Dailies Must Be Completable
Id
daily-completability
Description
Validates that daily tasks can be done in reasonable time
Severity
warning
Category
respect
Check
function validateDailyTasks(dailies, avgTaskTime) { const totalTime = dailies.reduce((sum, d) => sum + d.estimatedMinutes, 0);
if (totalTime > 45) { return { valid: false, totalTime: totalTime, message: Daily tasks take ${totalTime} min - max 45 min recommended }; }
return { valid: true }; }
Message
Daily tasks exceed 45-minute budget - respect player time
Implementation Guide
// Design dailies for 15-30 minute completion const dailyQuests = { maxActive: 3, timeEstimate: "15-30 minutes total", rollover: true, // Can stack up to 3 days skipPenalty: false // Missing a day doesn't break streaks };
Battle Pass Achievable
Id
battle-pass-pacing
Description
Validates battle pass can be completed by casual players
Severity
warning
Category
fairness
Check
function validateBattlePass(config) { const { seasonDays, totalTiers, xpPerTier, dailyXP, weeklyXP } = config;
// Assume casual: plays 4 days/week, 30 min/day const casualDays = Math.floor(seasonDays (4/7)); const casualXP = (casualDays dailyXP) + (Math.floor(seasonDays / 7) * weeklyXP); const casualTiers = Math.floor(casualXP / xpPerTier);
if (casualTiers < totalTiers) { return { valid: false, casualTiers: casualTiers, message: Casual player reaches tier ${casualTiers}/${totalTiers}, suggestion: "Reduce XP/tier or add catch-up mechanisms" }; }
return { valid: true }; }
Fix Template
// Battle pass pacing for 60-day season const battlePass = { totalTiers: 100, xpPerTier: 10000, totalXPNeeded: 1000000,
dailyXP: { quests: 3000, passivePlay: 2000 }, weeklyXP: 50000,
// Casual (4 days/week, 30 min): Completes by day 55 // Regular (5 days/week, 1 hour): Completes by day 40 // Hardcore: Completes by day 25 };
Streaks Should Have Grace
Id
streak-grace-period
Description
Checks that streak systems have grace periods
Severity
info
Category
psychology
Patterns
- streak.*reset|resetStreak|breakStreak
Check
function validateStreakGrace(streakConfig) { if (!streakConfig.gracePeriod && !streakConfig.freezeToken) { return { valid: false, message: "Streak system has no grace period or freeze option" }; } return { valid: true }; }
Fix Template
const streakSystem = { gracePeriod: 24 60 60 * 1000, // 24 hours grace freezeTokens: { earnedPer: "week", maxStored: 3, effect: "Freeze streak for 1 day" }, // Alternative: Degradation instead of reset degradation: { missedDay: -1, // Lose 1 day of streak, not reset to 0 minStreak: 0 } };
Prestige Must Add New Content
Id
prestige-unlocks-new-content
Description
Validates that each prestige tier unlocks new mechanics
Severity
warning
Category
design
Patterns
- prestigeUnlock|unlockOnPrestige|prestigeReward
Check
function validatePrestigeValue(prestigeTiers) { for (const [tier, rewards] of Object.entries(prestigeTiers)) { const hasNewContent = rewards.some(r => r.type === 'mechanic' || r.type === 'mode' || r.type === 'feature' );
if (!hasNewContent) { return { valid: false, tier: tier, message: Prestige tier ${tier} only gives stat bonuses - add new content }; } } return { valid: true }; }
Implementation Guide
// Each prestige should unlock something NEW to do const prestigeRewards = { 1: { statBonus: "+25% XP", newContent: "Unlock Crafting System" // New mechanic }, 2: { statBonus: "+50% XP", newContent: "Unlock Challenge Modes" // New mode }, 3: { statBonus: "+75% XP", newContent: "Unlock Character Customization" // New feature } };
Prestige Must Preserve Knowledge
Id
prestige-preserves-knowledge
Description
Checks that prestige doesn't erase learned unlocks
Severity
warning
Category
psychology
Patterns
- prestige|rebirth|ascension|newGamePlus
Check
function validatePrestigePreservation(prestigeConfig) { const mustPreserve = [ 'unlockedFeatures', 'discoveredRecipes', 'completedAchievements', 'cosmetics' ];
const preserved = prestigeConfig.persists || []; const missing = mustPreserve.filter(p => !preserved.includes(p));
if (missing.length > 0) { return { valid: false, missing: missing, message: Prestige resets ${missing.join(', ')} - these should persist }; } return { valid: true }; }