--- 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> = { 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> = {}; add(mod: { flat?: number; pct?: number; resist?: Partial> }) { 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, 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 (
매 {atk.type} → {def.armor}
{tag} (×{mult.toFixed(2)})
); } ``` ### 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