f8b21af4be
10_Wiki/Topics 대규모 정리: - 오류 캡처/미완성 stub 문서 227개 제거 - 교차폴더 중복 43클러스터 병합 (63파일 → redirect) - 링크명 정규화: 깨진 링크 수정·redirect 직결·개념 매핑 ~2,400건 - 카테고리 MOC 6개 신규 생성 - Graph 섹션 미해결 related-keyword 링크 10,058건 제거 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
179 lines
6.5 KiB
Markdown
179 lines
6.5 KiB
Markdown
---
|
||
id: wiki-2026-0508-platform-resistance
|
||
title: Platform Resistance
|
||
category: 10_Wiki/Topics
|
||
status: verified
|
||
canonical_id: self
|
||
aliases: [unit resistance, damage resistance, RTS armor]
|
||
duplicate_of: none
|
||
source_trust_level: B
|
||
confidence_score: 0.7
|
||
verification_status: applied
|
||
tags: [game-design, balance, combat, rts]
|
||
raw_sources: []
|
||
last_reinforced: 2026-05-10
|
||
github_commit: pending
|
||
tech_stack:
|
||
language: design
|
||
framework: game-design
|
||
---
|
||
|
||
# Platform Resistance
|
||
|
||
## 매 한 줄
|
||
> **"매 unit 의 platform / armor type 별 매 damage 감소 multiplier 시스템"**. 매 RTS / strategy / MOBA 의 rock-paper-scissors counter 의 backbone — 매 StarCraft 의 small/medium/large + light/armored, Warcraft 의 unarmored/light/medium/heavy/fortified 가 정형화한 패턴.
|
||
|
||
## 매 핵심
|
||
|
||
### 매 왜 필요
|
||
- **Counter-play**: 매 unit composition 의 rock-paper-scissors 만들기.
|
||
- **Build diversity**: 매 every unit 의 의미 부여.
|
||
- **Skill expression**: 매 적 조합 read → 매 본인 조합 적응.
|
||
- **Pacing**: 매 단순 HP 보다 매 양방향 trade-off.
|
||
|
||
### 매 components
|
||
1. **Damage type**: physical, magical, piercing, explosive, energy.
|
||
2. **Armor / Platform type**: light, medium, heavy, fortified, hero, structure, biological, mechanical.
|
||
3. **Multiplier matrix**: damage × armor → 매 actual damage scalar.
|
||
4. **Flat reduction (armor value)**: 매 추가 layer.
|
||
5. **Penetration / shred**: 매 reduction 무시 또는 감소.
|
||
|
||
### 매 formula 변형
|
||
- **Multiplicative table** (StarCraft 2): `dmg = base * matrix[atk][armor] - armor_value`.
|
||
- **Percent reduction** (LoL, MOBA): `taken = dmg * 100 / (100 + armor)`.
|
||
- **Hybrid**: 매 flat 빼고 매 % 곱하기.
|
||
- **Threshold**: 매 minimum damage floor (보통 1).
|
||
|
||
### 매 design dial
|
||
- **Granularity**: 매 3-5 type vs 매 10+ — 매 readability vs depth.
|
||
- **Magnitude**: 매 ±25%? ±100%? 매 큰 차이일수록 counter 강제.
|
||
- **Visibility**: 매 UI 에 명시 vs 매 숨김. 매 modern은 명시 권장.
|
||
- **Stack**: 매 buff/debuff 의 stack 규칙.
|
||
|
||
## 💻 패턴
|
||
|
||
### Damage matrix (declarative)
|
||
```ts
|
||
type DamageType = "physical" | "magical" | "piercing" | "explosive";
|
||
type ArmorType = "light" | "medium" | "heavy" | "fortified" | "structure";
|
||
|
||
const MATRIX: Record<DamageType, Record<ArmorType, number>> = {
|
||
physical: { light: 1.0, medium: 1.0, heavy: 1.0, fortified: 0.5, structure: 1.0 },
|
||
piercing: { light: 1.5, medium: 1.0, heavy: 0.75, fortified: 0.5, structure: 0.5 },
|
||
explosive: { light: 0.5, medium: 1.0, heavy: 1.5, fortified: 1.5, structure: 1.5 },
|
||
magical: { light: 1.25, medium: 1.0, heavy: 0.75, fortified: 1.0, structure: 0.25 },
|
||
};
|
||
|
||
function computeDamage(amount: number, dt: DamageType, at: ArmorType, armorVal: number, pen: number) {
|
||
const mult = MATRIX[dt][at];
|
||
const effectiveArmor = Math.max(0, armorVal - pen);
|
||
const reduced = amount * mult - effectiveArmor;
|
||
return Math.max(1, Math.floor(reduced));
|
||
}
|
||
```
|
||
|
||
### Percent-armor (MOBA style)
|
||
```ts
|
||
function applyPercentArmor(dmg: number, armor: number, penFlat: number, penPct: number) {
|
||
let a = armor * (1 - penPct) - penFlat;
|
||
if (a >= 0) return dmg * 100 / (100 + a);
|
||
return dmg * (2 - 100 / (100 - a)); // 매 negative armor 의 amplification
|
||
}
|
||
```
|
||
|
||
### Buff / debuff stack
|
||
```ts
|
||
class ArmorMods {
|
||
flat = 0; pct = 0; resists: Partial<Record<DamageType, number>> = {};
|
||
|
||
add(mod: { flat?: number; pct?: number; resist?: Partial<Record<DamageType, number>> }) {
|
||
this.flat += mod.flat ?? 0;
|
||
this.pct += mod.pct ?? 0;
|
||
for (const [k, v] of Object.entries(mod.resist ?? {})) {
|
||
this.resists[k as DamageType] = (this.resists[k as DamageType] ?? 0) + (v ?? 0);
|
||
}
|
||
}
|
||
apply(baseArmor: number) { return Math.max(0, baseArmor * (1 + this.pct) + this.flat); }
|
||
}
|
||
```
|
||
|
||
### ECS damage pipeline
|
||
```rust
|
||
fn damage_system(
|
||
mut events: EventReader<DamageEvent>,
|
||
mut q: Query<(&ArmorType, &mut Health, &Armor)>,
|
||
) {
|
||
for ev in events.read() {
|
||
if let Ok((at, mut hp, armor)) = q.get_mut(ev.target) {
|
||
let mult = damage_matrix(ev.kind, *at);
|
||
let reduced = (ev.amount as f32 * mult - armor.value as f32).max(1.0);
|
||
hp.0 = hp.0.saturating_sub(reduced as u32);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Tooltip / UI exposure
|
||
```tsx
|
||
function DamageTooltip({ atk, def }: Props) {
|
||
const mult = MATRIX[atk.type][def.armor];
|
||
const tag = mult > 1 ? "매 효과적" : mult < 1 ? "매 비효율" : "매 보통";
|
||
const color = mult > 1 ? "green" : mult < 1 ? "red" : "gray";
|
||
return (
|
||
<div>
|
||
<div>매 {atk.type} → {def.armor}</div>
|
||
<div style={{ color }}>{tag} (×{mult.toFixed(2)})</div>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
### Balance test harness
|
||
```ts
|
||
// 매 1v1 simulation, 매 victor 와 잔존 HP 의 distribution
|
||
function simulate(a: Unit, b: Unit, n=1000): { winA: number; avgHpRemain: number } {
|
||
let wins = 0; let hpSum = 0;
|
||
for (let i=0; i<n; i++) {
|
||
const r = duel(clone(a), clone(b));
|
||
if (r.winner === "A") { wins++; hpSum += r.hpRemain; }
|
||
}
|
||
return { winA: wins / n, avgHpRemain: hpSum / Math.max(wins, 1) };
|
||
}
|
||
// 매 target: 매 each pair 의 winA 가 0.4–0.6 (counter pair 만 외)
|
||
```
|
||
|
||
## 매 결정 기준
|
||
| 상황 | Approach |
|
||
|---|---|
|
||
| RTS, deep counter | Multiplicative matrix + flat armor |
|
||
| MOBA / ARPG | Percent armor + penetration |
|
||
| Casual / small unit pool | 3 type × 3 type, ±50% |
|
||
| Hardcore / pro scene | 5+ type, granular tuning + telemetry |
|
||
| New player onboarding | UI 에서 매 effective/ineffective 명시 |
|
||
|
||
**기본값**: 매 4 damage × 4 armor matrix, ±50% multiplier, flat armor secondary.
|
||
|
||
## 🔗 Graph
|
||
- 부모: [[게임 밸런싱|Game Balance]] · [[Combat System]]
|
||
|
||
## 🤖 LLM 활용
|
||
**언제**: 매 strategy/RTS/MOBA balance, 매 unit pool 디자인 / 매 counter-play 설계.
|
||
**언제 X**: 매 단순 deathmatch, 매 narrative-only — 매 의도 mismatch.
|
||
|
||
## ❌ 안티패턴
|
||
- **Hidden multipliers**: 매 player 가 모르는 매 ×2 가 곱해짐 → 매 frustration.
|
||
- **Flat-only**: 매 high-DPS unit 의 매 armor scaling 무력화.
|
||
- **Too granular**: 매 12 × 12 matrix → 매 readability 사망.
|
||
- **No penetration counter**: 매 high-armor 가 invincible.
|
||
- **Same multiplier everywhere**: 매 의미 없는 type 만 추가.
|
||
|
||
## 🧪 검증 / 중복
|
||
- Verified (Liquipedia SC2 / Wc3 mechanics, Riot LoL armor formula docs, GDC RTS balance talks).
|
||
- 신뢰도 A (genre 일반); B (특정 game 의 경우 변형 빈번).
|
||
|
||
## 🕓 Changelog
|
||
| 날짜 | 변경 |
|
||
|---|---|
|
||
| 2026-05-08 | Phase 1 |
|
||
| 2026-05-10 | Manual cleanup — damage matrix + armor formulas + balance harness |
|