docs(10_Wiki): 위키 전체 재구성 — Topic_* 폴더를 4개 카테고리로 통합 + 대규모 중복 제거
Topic_Agent/Topic_Blog/Topics/Topics_Biz/Topics_Meeting/Topics_Rag의 마크다운 지식 문서를 Topic_General/Topic_Programming/Topic_Graphic/Topic_Business 4개 카테고리로 재분류. - 중복 제거: frontmatter의 status:duplicate/merged + duplicate_of/redirect_to 필드로 자기 자신을 중복으로 선언한 리다이렉트 stub 1032개 제거, 완전 동일 내용 파일 472개 제거, 동일 파일명·다른 내용 충돌 시 더 큰(완전한) 버전만 유지(162개 제거) — 총 1639개 중복 제거. - 분류: 폴더 단위로 명확한 항목(AI_and_ML/Coding/Architecture 등 → Programming, Comfyui/Visual_Effects → Graphic, Topics_Biz/Topics_Meeting/사업 등 → Business, Poetic_Blog_Writing/창의성/Game_Design 등 → General)은 폴더 우선순위로, 나머지 혼재 폴더(Topic_Agent/Topic_Blog/Topics 루트/Thinking & Reasoning/Other/UI_UX_Assets)는 title/tags 키워드 스코어링으로 파일 단위 분류(불명확한 경우 General로 폴백). 원본 폴더명은 "From_*" 서브폴더로 보존해 추적 가능성 유지. - 최종 배치: Programming 2784 / General 1608 / Graphic 285 / Business 249 = 4926개 문서. - 에이전트 운영 상태(.astra/.agent/.obsidian/sessions/memory/_company/docs/lessons/_shared/src)는 지식 콘텐츠가 아니므로 재분류 대상에서 제외하고 원위치 유지. - Topics/Topic_email(상위 보호 폴더 Topic_email과 파일명 100% 중복) 삭제 — 보호 폴더 자체는 미변경. - 완전히 비게 된 Topic_Agent/Topic_Blog/Topics_Biz/Topics_Rag 폴더 제거.
This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
---
|
||||
id: wiki-2026-0508-dda
|
||||
title: Dynamic Difficulty Adjustment (DDA)
|
||||
category: 10_Wiki/Topics
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [DDA, dynamic difficulty, rubber banding, AI Director, flow state, adaptive difficulty]
|
||||
duplicate_of: none
|
||||
source_trust_level: B
|
||||
confidence_score: 0.85
|
||||
verification_status: applied
|
||||
tags: [game-design, dda, dynamic-difficulty, flow, ai-director, player-modeling, adaptive]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: game design
|
||||
applicable_to: [Game Design, Adaptive Learning, ML Curriculum]
|
||||
---
|
||||
|
||||
# Dynamic Difficulty Adjustment (DDA)
|
||||
|
||||
## 매 한 줄
|
||||
> **"매 player skill 의 real-time 매 difficulty 의 dial"**. Csikszentmihalyi 의 Flow zone 의 maintain. 매 Left 4 Dead 의 AI Director, 매 racing 의 rubber banding. 매 modern: 매 ML-driven player model + 매 ethical detect (gaming the system). 매 LMS adaptive learning 의 same principle.
|
||||
|
||||
## 매 핵심
|
||||
|
||||
### 매 mechanism
|
||||
1. **Player metric**: 매 win rate, HP remaining, time, deaths.
|
||||
2. **Skill estimate**: 매 sliding window of recent.
|
||||
3. **Adjustment**: 매 enemy stats / spawn / hint.
|
||||
4. **Subtle**: 매 obvious 의 player 의 frustrate.
|
||||
|
||||
### 매 famous example
|
||||
- **Left 4 Dead AI Director** (Valve): 매 zombie spawn 의 control.
|
||||
- **Resident Evil 4**: 매 difficulty 의 silent.
|
||||
- **Mario Kart**: 매 rubber banding (item, speed).
|
||||
- **Crash Bandicoot**: 매 attempt 후 의 adjust.
|
||||
- **Mario AI** (research): 매 student 의 player.
|
||||
|
||||
### 매 method
|
||||
- **Heuristic**: 매 simple rule.
|
||||
- **Bayesian player model**: 매 skill posterior.
|
||||
- **RL-based**: 매 difficulty 의 policy 학습.
|
||||
- **MCTS-based**: 매 lookahead.
|
||||
|
||||
### 매 ethical / design constraint
|
||||
- **Subtle**: 매 player 의 perception X.
|
||||
- **Fair feel**: 매 effort-reward.
|
||||
- **Not patronize**: 매 매 player 의 skill ↑ 의 acknowledge.
|
||||
- **No gaming detection**: 매 player 의 intentional poor 의 abuse.
|
||||
|
||||
### 매 응용
|
||||
1. **Action / shooter**: 매 enemy difficulty.
|
||||
2. **Racing**: 매 rubber banding.
|
||||
3. **Puzzle**: 매 hint.
|
||||
4. **MMO raid**: 매 boss adjustment.
|
||||
5. **Edu game / LMS**: 매 question difficulty.
|
||||
6. **AI tutor**: 매 explanation depth.
|
||||
7. **Aim training**: 매 [[Cognitive Training Software (eg Aim Lab_KovaaKs)]] 의 adaptive scenario.
|
||||
|
||||
### 매 modern AI 응용
|
||||
- **Player modeling**: 매 ML 의 skill 의 estimate.
|
||||
- **RL-based DDA**: 매 difficulty controller 의 train.
|
||||
- **Generative content**: 매 procedurally adapted level.
|
||||
|
||||
## 💻 패턴
|
||||
|
||||
### Sliding window skill estimator
|
||||
```ts
|
||||
class SkillEstimator {
|
||||
private history: number[] = [];
|
||||
|
||||
recordOutcome(win: boolean, time: number, hpLost: number) {
|
||||
const score = (win ? 1 : 0) * (60 / Math.max(time, 1)) * (1 - hpLost / 100);
|
||||
this.history.push(score);
|
||||
if (this.history.length > 20) this.history.shift();
|
||||
}
|
||||
|
||||
estimateSkill(): number {
|
||||
if (!this.history.length) return 0.5;
|
||||
return this.history.reduce((a, b) => a + b, 0) / this.history.length;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### AI Director-style (probabilistic)
|
||||
```ts
|
||||
class AIDirector {
|
||||
threatLevel = 0; // 0-1
|
||||
|
||||
update(player: Player) {
|
||||
const recentDamage = player.recentDamageTaken();
|
||||
const ammoLow = player.ammo < 30;
|
||||
const downtime = player.timeWithoutCombat();
|
||||
|
||||
// 매 raise threat 의 calm
|
||||
if (downtime > 30) this.threatLevel = Math.min(1, this.threatLevel + 0.05);
|
||||
|
||||
// 매 ease 의 stress
|
||||
if (recentDamage > 50 || ammoLow) this.threatLevel = Math.max(0, this.threatLevel - 0.1);
|
||||
}
|
||||
|
||||
shouldSpawnEnemy(): boolean {
|
||||
return Math.random() < this.threatLevel;
|
||||
}
|
||||
|
||||
enemyComposition() {
|
||||
if (this.threatLevel < 0.3) return 'easy_pack';
|
||||
if (this.threatLevel < 0.7) return 'mixed';
|
||||
return 'horde';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Rubber banding (racing)
|
||||
```ts
|
||||
function applyRubberBand(car: Car, leader: Car) {
|
||||
const distanceBehind = leader.position - car.position;
|
||||
|
||||
// 매 subtle catch-up
|
||||
if (distanceBehind > 100) {
|
||||
car.maxSpeed *= 1.05; // 매 5% boost
|
||||
car.itemDropChance *= 1.5; // 매 better items
|
||||
} else if (distanceBehind < -50) {
|
||||
// 매 leader 의 small handicap
|
||||
car.maxSpeed *= 0.98;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Bayesian player model (IRT-inspired)
|
||||
```python
|
||||
import numpy as np
|
||||
|
||||
class BayesianPlayer:
|
||||
def __init__(self, prior_mean=0, prior_var=1):
|
||||
self.skill_mean = prior_mean
|
||||
self.skill_var = prior_var
|
||||
|
||||
def update(self, item_difficulty, success):
|
||||
"""매 item response theory."""
|
||||
# 매 expected probability
|
||||
p_success = 1 / (1 + np.exp(-(self.skill_mean - item_difficulty)))
|
||||
|
||||
# 매 posterior update (simplified Kalman)
|
||||
info = p_success * (1 - p_success)
|
||||
self.skill_var = 1 / (1 / self.skill_var + info)
|
||||
self.skill_mean += self.skill_var * info * (int(success) - p_success)
|
||||
|
||||
def select_difficulty(self, items, target_p=0.7):
|
||||
"""매 just-beyond comfort."""
|
||||
return min(items, key=lambda d: abs(
|
||||
1 / (1 + np.exp(-(self.skill_mean - d))) - target_p
|
||||
))
|
||||
```
|
||||
|
||||
### RL-based DDA
|
||||
```python
|
||||
class DifficultyController:
|
||||
"""매 RL 의 매 player 의 engagement 의 maximize."""
|
||||
|
||||
def __init__(self):
|
||||
self.policy = QNetwork()
|
||||
|
||||
def select_difficulty(self, player_state):
|
||||
actions = ['decrease', 'maintain', 'increase']
|
||||
q_values = self.policy(player_state)
|
||||
return actions[q_values.argmax()]
|
||||
|
||||
def reward(self, player_state):
|
||||
# 매 engagement = match length + retention + flow indicator
|
||||
return (
|
||||
player_state.session_length * 0.3 +
|
||||
(1 if player_state.continued_after_match else 0) * 0.5 +
|
||||
player_state.subjective_satisfaction * 0.2
|
||||
)
|
||||
```
|
||||
|
||||
### Detect gaming the system
|
||||
```python
|
||||
def detect_throw_match(player_history):
|
||||
"""매 player 의 intentional poor 의 detect."""
|
||||
recent = player_history[-10:]
|
||||
|
||||
# 매 sudden drop in performance
|
||||
historical_avg = np.mean([h.score for h in player_history[:-10]])
|
||||
recent_avg = np.mean([h.score for h in recent])
|
||||
|
||||
if recent_avg < historical_avg * 0.4:
|
||||
return 'WARN: possible throwing for DDA exploit'
|
||||
return None
|
||||
```
|
||||
|
||||
### Subtle communication (UX)
|
||||
```ts
|
||||
function hidePlayerSeesAdjustment() {
|
||||
// 매 ❌ 매 obvious
|
||||
if (showText) showMessage('Difficulty decreased due to your performance');
|
||||
|
||||
// 매 ✅ 매 silent
|
||||
// 매 just adjust enemy stats internally.
|
||||
// 매 player 의 say "I'm getting better!" 매 actually 의 difficulty 의 ease.
|
||||
}
|
||||
```
|
||||
|
||||
### A/B test (DDA effectiveness)
|
||||
```python
|
||||
def ab_test_dda(players_a_no_dda, players_b_with_dda):
|
||||
return {
|
||||
'session_length_a': mean(p.session_length for p in players_a_no_dda),
|
||||
'session_length_b': mean(p.session_length for p in players_b_with_dda),
|
||||
'retention_d7_a': retention(players_a_no_dda, days=7),
|
||||
'retention_d7_b': retention(players_b_with_dda, days=7),
|
||||
'satisfaction_a': mean(p.satisfaction for p in players_a_no_dda),
|
||||
'satisfaction_b': mean(p.satisfaction for p in players_b_with_dda),
|
||||
}
|
||||
```
|
||||
|
||||
### LMS adaptive (similar pattern)
|
||||
```python
|
||||
def adaptive_quiz(student, item_pool):
|
||||
skill = student.estimated_skill
|
||||
|
||||
# 매 zone of proximal development
|
||||
target_difficulty = skill + 0.5 # 매 just-beyond
|
||||
next_item = min(item_pool, key=lambda i: abs(i.difficulty - target_difficulty))
|
||||
|
||||
response = present(next_item, student)
|
||||
student.update_skill(next_item.difficulty, response.correct)
|
||||
return next_item
|
||||
```
|
||||
|
||||
### Procedural difficulty (level generation)
|
||||
```python
|
||||
def generate_level(player_skill):
|
||||
return {
|
||||
'enemy_count': int(10 + player_skill * 20),
|
||||
'enemy_hp': 100 * (1 + player_skill * 0.5),
|
||||
'puzzle_complexity': int(player_skill * 5),
|
||||
'time_limit': 300 - int(player_skill * 100),
|
||||
'powerup_density': max(0.1, 0.5 - player_skill * 0.3),
|
||||
}
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| Genre | DDA approach |
|
||||
|---|---|
|
||||
| Action / Shooter | AI Director (Valve-style) |
|
||||
| Racing | Rubber banding (subtle) |
|
||||
| Puzzle | Hint frequency |
|
||||
| MMO raid | Stat-tier difficulty |
|
||||
| RPG | Side content |
|
||||
| Edu game | Adaptive item (IRT) |
|
||||
| Esports / competitive | NO DDA (fairness) |
|
||||
| Casual mobile | Subtle DDA + retention focus |
|
||||
|
||||
**기본값**: 매 Bayesian player model + 매 subtle adjust + 매 A/B test + 매 detect gaming.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[Game-Design]] · [[Adaptive-Learning]]
|
||||
- 변형: [[AI-Director]] · [[Rubber-Banding]] · [[Player-Modeling]]
|
||||
- 응용: [[Cognitive Training Software (eg Aim Lab_KovaaKs)]] · [[Corporate-LMS-Training]] (adaptive) · [[Cognitive-Evaluation-Theory]]
|
||||
- Adjacent: [[Default Mode Network (DMN)]] (flow) · [[Deliberate-Practice]] · [[Combined Arms (제병협동) 전술]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 매 game design. 매 adaptive learning system. 매 personalized challenge.
|
||||
**언제 X**: 매 esports / fairness-critical (no DDA).
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **Obvious DDA**: 매 player 의 perception → 매 motivation lose.
|
||||
- **Patronizing**: 매 매 player 의 skill 의 acknowledge X.
|
||||
- **Punishing skilled play**: 매 better → 매 harder feel of unfair.
|
||||
- **No detection 의 throw**: 매 exploit.
|
||||
- **DDA in competitive**: 매 fairness violation.
|
||||
- **Too aggressive adjust**: 매 whiplash.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (Csikszentmihalyi Flow, Valve AI Director paper, Hunicke MDA).
|
||||
- 신뢰도 B.
|
||||
- Related: [[Cognitive Training Software (eg Aim Lab_KovaaKs)]] · [[Corporate-LMS-Training]] · [[Cognitive-Evaluation-Theory]] · [[Deliberate-Practice]] · [[Combined Arms (제병협동) 전술]].
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — mechanism + 매 AI Director / rubber band / Bayesian / RL / detect-throw code |
|
||||
Reference in New Issue
Block a user