---
id: wiki-2026-0508-데이터-기반-밸런싱-data-driven-balancing
title: 데이터 기반 밸런싱 (Data-Driven Balancing)
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Data-Driven Balancing, 게임 밸런싱, Telemetry Balancing]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [game-design, balancing, telemetry, analytics, live-ops]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
  language: Python
  framework: pandas / DuckDB / Bayesian
---

# 데이터 기반 밸런싱 (Data-Driven Balancing)

## 매 한 줄
> **"매 디자이너의 직관 + 매 telemetry 의 evidence = 매 healthy meta"**. 매 player behavior 의 raw log 의 aggregation, 매 win-rate / pick-rate / progression curve 의 analysis 의 통한 매 game economy / character / weapon 의 systematic tuning. 매 2010s MOBA / FPS 의 표준화, 매 2026 의 ML-assisted (UCB / Bayesian A/B) 의 진화.

## 매 핵심

### 매 measurement primitives
- **Win rate**: 매 character / build / matchup 의 50% 의 deviation.
- **Pick rate / Ban rate**: 매 perceived strength 의 proxy.
- **Time-to-kill (TTK)**: 매 weapon 의 lethality consistency.
- **Engagement curve**: 매 session length, 매 retention D1/D7/D30.
- **Economy velocity**: 매 currency / resource 의 sink/source ratio.

### 매 analytical method
- **Cohort analysis**: 매 skill bracket 의 separate (bronze ≠ challenger).
- **Bayesian A/B**: 매 small sample 의 conservative inference.
- **Causal inference**: 매 patch 의 win-rate change 의 confounders 의 control.
- **Multi-armed bandit (UCB1)**: 매 live tuning 의 explore/exploit balance.

### 매 응용
1. 매 League of Legends — 매 champion winrate 의 50% ± 2% 의 target band.
2. 매 Hearthstone — 매 deck winrate 의 buff/nerf trigger.
3. 매 Counter-Strike — 매 weapon 의 economy + accuracy tuning.
4. 매 Genshin Impact — 매 banner pull rate 의 monetization curve.

## 💻 패턴

### Win-rate aggregation (DuckDB)
```python
import duckdb

con = duckdb.connect("matches.db")
con.execute("""
  SELECT champion_id,
         elo_bracket,
         COUNT(*) AS games,
         SUM(won)::FLOAT / COUNT(*) AS winrate,
         STDDEV(won) / SQRT(COUNT(*)) AS se
  FROM match_participants
  WHERE patch = '14.10' AND games_played > 30
  GROUP BY champion_id, elo_bracket
  HAVING games > 1000
  ORDER BY winrate DESC
""").fetchdf()
```

### Bayesian winrate (PyMC)
```python
import pymc as pm
import numpy as np

wins, games = 156, 312
with pm.Model() as m:
    p = pm.Beta("p", alpha=1, beta=1)  # 매 uniform prior
    pm.Binomial("obs", n=games, p=p, observed=wins)
    trace = pm.sample(2000, tune=1000, progressbar=False)

ci_low, ci_high = np.percentile(trace.posterior["p"], [2.5, 97.5])
print(f"매 winrate 95% CI: [{ci_low:.3f}, {ci_high:.3f}]")
```

### Multi-armed bandit live tuning
```python
import numpy as np

class UCB1:
    def __init__(self, n_arms):
        self.counts = np.zeros(n_arms)
        self.values = np.zeros(n_arms)

    def select(self):
        t = self.counts.sum() + 1
        bonus = np.sqrt(2 * np.log(t) / np.maximum(self.counts, 1))
        return int(np.argmax(self.values + bonus))

    def update(self, arm, reward):
        self.counts[arm] += 1
        n = self.counts[arm]
        self.values[arm] += (reward - self.values[arm]) / n

# 매 weapon damage variant 5개 의 live test
bandit = UCB1(n_arms=5)
```

### Cohort segmentation
```python
def bracket(mmr: int) -> str:
    if mmr < 1200: return "bronze"
    if mmr < 1800: return "silver"
    if mmr < 2400: return "gold"
    if mmr < 3000: return "platinum"
    return "challenger"

df["bracket"] = df["mmr"].map(bracket)
pivot = df.pivot_table(
    index="champion_id", columns="bracket",
    values="won", aggfunc="mean"
)
```

### Causal patch impact (DiD)
```python
# 매 patch 14.10 의 champion X 의 buff 의 impact
# Difference-in-Differences: (post_X - pre_X) - (post_control - pre_control)
import pandas as pd

pre = df[df.patch == "14.09"]
post = df[df.patch == "14.10"]
treated = "champion_X"

did = (post[post.champ == treated].won.mean()
     - pre[pre.champ == treated].won.mean()) - \
      (post[post.champ != treated].won.mean()
     - pre[pre.champ != treated].won.mean())
print(f"매 isolated buff impact: {did:+.3f}")
```

### Telemetry event schema (Protobuf)
```protobuf
message MatchEvent {
  string match_id = 1;
  uint64 timestamp_ms = 2;
  enum Type { KILL = 0; DEATH = 1; OBJECTIVE = 2; ITEM_BUY = 3; }
  Type type = 3;
  uint32 actor_id = 4;
  uint32 target_id = 5;
  map<string, double> metrics = 6;  // damage, gold, etc.
}
```

### Economy sink/source dashboard
```sql
WITH flow AS (
  SELECT date_trunc('day', ts) AS day,
         SUM(CASE WHEN delta > 0 THEN delta ELSE 0 END) AS source,
         SUM(CASE WHEN delta < 0 THEN -delta ELSE 0 END) AS sink
  FROM currency_log GROUP BY 1
)
SELECT day, source, sink, source - sink AS inflation
FROM flow ORDER BY day DESC LIMIT 30;
```

## 매 결정 기준
| 상황 | Approach |
|---|---|
| 매 새 champion 의 launch 첫 주 | Bayesian + Wide CI — 매 sample 의 작음 |
| 매 mature meta 의 fine-tune | Frequentist + DiD — 매 large N |
| 매 monetization tuning | Multi-armed bandit — 매 revenue 의 explore/exploit |
| 매 cross-bracket disparity | Cohort separation 매 mandatory |
| 매 strong narrative pressure | Designer override 의 telemetry 의 위 |

**기본값**: 매 cohort-segmented winrate + 매 Bayesian CI + 매 designer review.

## 🔗 Graph
- 부모: [[Game_Design]] · [[Live_Ops]]
- 변형: [[Multi_Armed_Bandit]]
- Adjacent: [[Telemetry]] · [[Bayesian_Inference]]

## 🤖 LLM 활용
**언제**: 매 patch note 의 generation, 매 telemetry summary 의 narrative, 매 anomaly detection 의 first-pass.
**언제 X**: 매 final tuning decision (designer judgment 의 loadbearing).

## ❌ 안티패턴
- **Aggregate-only**: 매 cohort 의 무시 — bronze 의 OP 가 challenger 의 weak.
- **Winrate worship**: 매 50% 의 강제 — 매 niche pick 의 destroy.
- **Sample-too-small nerf**: 매 100 games 의 nerf — 매 noise 의 reaction.
- **No control**: 매 patch 의 effect 의 confounder 의 ignore.
- **Telemetry without designer**: 매 game feel 의 loss.

## 🧪 검증 / 중복
- Verified (Riot Games 의 dev blog, Blizzard 의 GDC talk, Valve 의 CSGO economy paper).
- 신뢰도 A.

## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — full content with telemetry/Bayesian/bandit patterns |
