arena/elite.md

14 KiB

Elite 캐릭터 구현 문서

현재 상태

이 문서는 major 브랜치에서 30d7be41bef258685bf67219f2fcf77334c191f8를 기준으로 구현한 elite 압축 전투 규칙을 설명한다. 이전 WIP에서 누락됐던 월드 이펙트 비율 상수까지 포함해 구현했으며, npm run build로 빌드를 검증했다.

기능 의도

1. 인원 압축

  • 참가자 입력은 기존의 닉네임*N 형식을 유지한다.
  • FIGHTER.ELITE.RANDOMIZED_COMPRESSION.MIN_TEAM_SIZE 미만에서는 STACK_SIZE = 100명마다 elite fighter 1개체를 소환한다.
  • 소규모 팀에서 100명으로 묶이지 않는 나머지는 normal fighter를 1명당 1개체씩 소환한다.
  • 현재 MIN_TEAM_SIZE = 100 설정에서는 100명 블록이 있는 입력부터 랜덤 압축 대상이다.
  • 일반 랜덤 압축은 100명 블록마다 ELITE_BLOCK_PROBABILITY = 0.6으로 elite 압축 여부를 판정한다.
  • 사용자가 입력한 총 fighter 수가 PERFORMANCE.LARGE_BATTLE_FIGHTER_THRESHOLD보다 크면 LARGE_BATTLE_ELITE_BLOCK_PROBABILITY = 0.8을 사용해 elite 압축 비율을 높인다. 예: Alice*4000은 40개 블록 중 장기 평균 32개가 elite 32개체로 압축되어 3,200명을 대표하고, 나머지 8개 블록은 normal 800개체로 실제 렌더링된다.
  • 대규모 전투에서는 PERFORMANCE.LARGE_BATTLE_RENDERED_FIGHTER_LIMIT도 적용한다. 랜덤 판정 후 실제 렌더링 수가 제한을 넘으면 normal 100명 블록이나 remainder 묶음을 elite 그룹으로 추가 승격한다.
  • 팀 카드는 대표 인원 합계 대신 생존 렌더 구성을 E : <elite 수> | N : <normal 수>로 표시한다.
  • 사망 통계, 관전 판정, 밀집 구역 표적 선정은 계속 stackCount 합계를 사용한다.

2. Elite 스탯

Update: elite magic attack effects now use FIGHTER.ELITE.ATTACK_EFFECT_SCALE_MULTIPLIER on top of the normal FIGHTER.ATTACK_EFFECT_SCALE_MULTIPLIER, so spell visuals can be tuned separately from the elite fighter body scale.

구현된 상수:

export const FIGHTER = {
  // ...
  ATTACK_EFFECT_SCALE_MULTIPLIER: 1,
  ELITE: {
    TYPE: ["melee", "magic"],
    STACK_SIZE: 100,
    VISUAL_SCALE_MULTIPLIER: 5,
    ATTACK_EFFECT_SCALE_MULTIPLIER: 5,
    HP_BONUS_RATIO: 1,
    ATTACK_RANGE_MULTIPLIER: 1.5,
    ATTACK_DAMAGE_BONUS_MULTIPLIER: 1,
    ATTACK_DAMAGE_STACK_EXPONENT: 0.1,
    ATTACK_SPEED_BONUS_MULTIPLIER: 1,
    ATTACK_SPEED_STACK_EXPONENT: 0.1,
    MOVE_SPEED_BONUS_MULTIPLIER: 1,
    MOVE_SPEED_STACK_EXPONENT: 0,
    RANDOMIZED_COMPRESSION: {
      MIN_TEAM_SIZE: 100,
      ELITE_BLOCK_PROBABILITY: 0.6,
      LARGE_BATTLE_ELITE_BLOCK_PROBABILITY: 0.8,
    },
  },
};

export const PERFORMANCE = {
  LARGE_BATTLE_FIGHTER_THRESHOLD: 3000,
  LARGE_BATTLE_RENDERED_FIGHTER_LIMIT: 1200,
  // ...
};

elite 계산 의도:

const attackDamageMultiplier = 1
  + FIGHTER.ELITE.ATTACK_DAMAGE_BONUS_MULTIPLIER
    * (stackCount ** FIGHTER.ELITE.ATTACK_DAMAGE_STACK_EXPONENT - 1);
const visualScale = FIGHTER.SCALE * FIGHTER.ELITE.VISUAL_SCALE_MULTIPLIER;
const rangeBonus =
  (visualScale - FIGHTER.SCALE) * (FIGHTER.HITBOX_WIDTH / 2);

damageMin = baseDamageMin * attackDamageMultiplier;
damageMax = baseDamageMax * attackDamageMultiplier;
maxHp = baseMaxHp * stackCount * FIGHTER.ELITE.HP_BONUS_RATIO;
attackRange =
  baseAttackRange * FIGHTER.ELITE.ATTACK_RANGE_MULTIPLIER + rangeBonus;
attackSpeedMultiplier *= 1 + FIGHTER.ELITE.ATTACK_SPEED_BONUS_MULTIPLIER
  * (stackCount ** FIGHTER.ELITE.ATTACK_SPEED_STACK_EXPONENT - 1);
moveSpeedMultiplier *= 1 + FIGHTER.ELITE.MOVE_SPEED_BONUS_MULTIPLIER
  * (stackCount ** FIGHTER.ELITE.MOVE_SPEED_STACK_EXPONENT - 1);
  • 피해량, 공격속도, 이동속도 보너스는 각각 FIGHTER.ELITE.*_BONUS_MULTIPLIER*_STACK_EXPONENT로 조정한다. multiplier 0은 추가 보너스 없음이고, multiplier 1은 설정한 stack 곡선을 그대로 적용한다.
  • HP는 압축 인원수와 현재 FIGHTER.ELITE.HP_BONUS_RATIO = 1 설정에 따라 선형 비례한다.
  • 이동속도에는 stackCount 보정을 적용하지 않는다. 공격 DPS와 생존력만 대표 인원에 맞춰 증가시키고, 거대 elite의 전장 이동은 일반 이동 규칙을 유지한다.

3. 처치 보너스 비활성화

  • elite는 여러 명의 피해량과 체력을 하나의 객체로 대표하므로, 물리 객체 기준의 처치 1회에 회복/성장 보너스를 주면 실제 대표 인원 기준으로 보상이 과대 적용된다.
  • COMBAT.KILL_REWARD_ENABLED = false를 기본 정책으로 두고, 모든 fighter의 처치 회복, 크기 성장, 공격속도/이동속도 보너스, 회복 이펙트를 비활성화한다.
  • 킬로그, 사망 통계, 승패 판정은 계속 동작한다. 기존 보너스 구현은 별도 모드가 필요할 경우 명시적으로 재활성화할 수 있도록 코드에 남겨 둔다.

4. Elite 대상 피해 이원화

구현된 치명타 상수:

COMBAT.CRITICAL_DAMAGE_PERCENT = 0.1;
COMBAT.NORMAL_CRITICAL_DAMAGE_MULTIPLIER = 2;

의도한 판정:

  • normal 대상 치명타: 일반 랜덤 피해의 2배를 적용한다.
  • elite 대상 치명타: maxHp의 10% 피해를 적용하되, 일반 타격 피해보다 낮아지지 않게 한다.
  • normal 대상 메테오/냉기: 기존 고정 피해인 WORLD_EFFECT.METEOR_DAMAGE, WORLD_EFFECT.FROST_DAMAGE를 유지한다.
  • elite 대상 메테오: maxHp의 40% 피해를 적용한다.
  • elite 대상 냉기: maxHp의 20% 피해를 적용한다.

이전 WIP에서 누락됐고 이번 구현에서 보완한 상수:

WORLD_EFFECT.METEOR_DAMAGE_PERCENT = 0.4;
WORLD_EFFECT.FROST_DAMAGE_PERCENT = 0.2;

위 두 값은 src/constants.js에 정의되어 elite 월드 이펙트 피해 계산에 사용된다.

구현 변경 지점

src/constants.js

  • COMBAT.CRITICAL_DAMAGE_PERCENT, COMBAT.NORMAL_CRITICAL_DAMAGE_MULTIPLIER를 추가했다.
  • COMBAT.KILL_REWARD_ENABLED = false를 추가해 처치 보너스를 비활성화했다.
  • WORLD_EFFECT.METEOR_DAMAGE_PERCENT, WORLD_EFFECT.FROST_DAMAGE_PERCENT를 추가했다.
  • fighter 도메인 아래 FIGHTER.ELITE 키로 elite 상수를 관리한다.
  • 일반 마법 공격 이펙트는 FIGHTER.ATTACK_EFFECT_SCALE_MULTIPLIER, elite 마법 공격 이펙트는 FIGHTER.ELITE.ATTACK_EFFECT_SCALE_MULTIPLIER로 조정한다.
  • 카메라/렌더/worker 성능 리팩터링 없이 elite 밸런스 상수만 추가했다.

src/game/match/matchSetup.js

  • 기존에는 team.size만큼 실제 fighter plan을 만들었다.
  • 임계값 미만 팀은 완전한 100명 블록마다 stackCount: 100, isElite: true plan을 하나 만들고, 나머지는 stackCount: 1, isElite: false plan으로 유지한다.
  • 임계값 이상 팀은 완전한 100명 블록마다 RANDOMIZED_COMPRESSION.ELITE_BLOCK_PROBABILITY로 elite 여부를 판정한다. 성공 블록은 stackCount: 100인 elite 하나가 되고, 실패 블록은 normal 100개체로 렌더링한다.
  • 전체 입력 fighter 수가 PERFORMANCE.LARGE_BATTLE_FIGHTER_THRESHOLD보다 크면 RANDOMIZED_COMPRESSION.LARGE_BATTLE_ELITE_BLOCK_PROBABILITY를 사용한다.
  • 랜덤 압축 결과가 PERFORMANCE.LARGE_BATTLE_RENDERED_FIGHTER_LIMIT를 넘으면 실패한 normal 100명 블록이나 normal remainder 묶음을 elite 그룹으로 승격해 실제 렌더링 개체 수를 제한한다.
  • 스폰 좌표 배열은 요청 인원 기준으로 생성하며, 각 elite는 대표하는 100명 블록의 첫 스폰 지점을 사용하고 나머지 normal은 대응하는 개별 스폰 지점을 사용한다.

src/game/match/arenaMatchRuntime.js

  • 팀 크기 동기화는 물리 Sprite 수 대신 stackCount 합계를 사용한다.
  • elite plan에는 spawnMultiplier를 적용하지 않아 대표 스택 전체가 한 번에 복제되지 않게 한다. normal plan의 기존 trait 동작은 유지한다.

src/game/fighter/fighterFactory.js

  • stackCount, isElite를 입력으로 받고 HP, 피해량, 사거리, 외형 크기를 계산한다.
  • elite의 baseScaleX/baseScaleY도 큰 외형 기준으로 저장한다. 현재는 킬 보너스가 꺼져 있지만, 별도 모드에서 다시 활성화할 경우 elite 기준 크기를 보존한다.
  • 기존 Sprite 기반 createFighter()에만 적용했으며, elite는 splitOnDeath를 사용하지 않는다.

src/game/fighter/fighterSelection.js

  • pickFightersForSetups()는 elite plan에 FIGHTER.ELITE.TYPE과 일치하는 스킨만 배정한다.
  • normal plan은 기존과 동일하게 전체 fighter manifest에서 스킨을 선택한다.

src/game/combat/combat.js

  • 치명타 판정을 normal 즉사에서 normal 2배 피해 / elite 최대 HP 비례 피해로 바꿨다.
  • 월드 이펙트 함수 인자를 고정 damage 값에서 "meteor"/"frost" 타입으로 바꾸고, 대상이 elite인지에 따라 고정 피해와 비율 피해를 나눈다.
  • 공격력 계산은 FIGHTER.ELITE.ATTACK_DAMAGE_*, 공격속도와 이동속도 계산은 FIGHTER.ELITE.ATTACK_SPEED_*FIGHTER.ELITE.MOVE_SPEED_* 상수를 사용한다.
  • instant-spell 공격 이펙트 크기는 상수 기반으로 계산하고, elite magic 스킨이면 FIGHTER.ELITE.ATTACK_EFFECT_SCALE_MULTIPLIER를 추가 적용한다.
  • 처치 흐름은 로그와 사망 처리는 유지하면서 COMBAT.KILL_REWARD_ENABLEDfalse일 때 applyKillReward()를 실행하지 않는다.
  • 기준 커밋에 존재하는 Sprite 전투 경로인 applyHit(), applyWorldEffectDamage(), fighterAttackSpeedMultiplier()만 수정했다.

src/game/combat/worldEffects.js

  • 메테오/냉기 낙하 처리에서 effect type을 applyWorldEffectDamage()로 전달한다.
  • 밀집 구역 계산은 stackCount를 가중치로 사용해 elite가 대표하는 인원을 월드 이펙트 표적 선정에 반영한다.

src/game/arena/ArenaScene.js

  • 사망 통계 누적 값을 + 1 대신 + (fighter.stackCount || 1)로 바꿨다.
  • elite가 죽으면 압축된 인원 전체가 오늘의 사망 통계에 기록되어야 한다.

src/game/arena/arenaSpectatorCamera.js

  • 관전 진입 임계값, 열세 팀 비교, 평균 포커스 좌표는 stackCount를 가중해 대규모 압축 팀이 시작 즉시 최종 교전으로 오판되지 않게 한다.

src/ui/arenaScoreboard.js

  • 살아 있는 elite 객체 수와 normal 객체 수를 각각 계산해 E : <elite> | N : <normal> 형식으로 표시한다.
  • 예를 들어 Alice*4000의 평균 구성은 E : 32 | N : 800 부근으로 표시되어 실제 렌더링되는 군세 구성을 바로 확인할 수 있다.

src/game/fighter/fighterModel.js, src/game/fighter/fighterAdapter.js

  • WIP 당시에는 stackCountisElite를 모델 브리지에도 추가했다.
  • 이 두 모듈은 30d7be4 이후 대규모 전투 최적화 커밋에서 추가된 구조이므로, 이번 롤백 기준에서는 elite 재구현의 선행 조건이 아니다.

의도적으로 포함하지 않은 변경

  • fighterLodWorker.js, aggregateCombatWorker.js 제거 또는 대체
  • 모델 전투/LOD/worker 경로 전면 단순화
  • 렌더 캔버스 크기, 카메라 줌, minimap throttle, HUD 파일 분리
  • agent.md 전체를 elite 전용 구조로 축약하는 변경

위 항목은 이전 WIP에 섞여 있었지만, elite 캐릭터 기능의 최소 구현과 독립적인 리팩터링이므로 포함하지 않았다.

구현 흐름

  1. src/constants.jsFIGHTER.ELITE에 elite 타입, 스탯, 공격력/속도, 랜덤 압축 설정을 묶고, 치명타 비율/배수와 메테오/냉기 elite 비율 상수를 추가한다.
  2. matchSetup.js에서 소규모 입력은 100명 단위로 압축하고, 대규모 입력은 각 100명 블록을 확률적으로 elite 한 개체 또는 normal 100개체로 생성한다.
  3. fighterFactory.js의 기존 Sprite 생성 경로에서 elite 외형, HP, 공격력, 사거리를 계산한다.
  4. fighterSelection.js에서 elite plan에는 근거리 스킨만 할당한다.
  5. combat.jsworldEffects.js에서 normal/elite 피해 판정을 나눈다.
  6. COMBAT.KILL_REWARD_ENABLED = false로 처치 회복/성장 보너스를 차단한다.
  7. ArenaScene.js의 사망 통계는 stackCount 합산을 유지하고, arenaScoreboard.js 팀 카드는 생존 elite/normal 객체 수를 분리 표시한다.
  8. agent.md, context/core.md, context/combat.md, context/fighter.md, context/match-ui.md, context/arena.md, todo.md에 구현 규칙을 기록한다.

검증 체크리스트

  • Alice*1은 이전과 동일하게 normal 1개체만 생성된다.
  • Alice*99는 normal 99개체로 생성된다.
  • 현재 임계값 100에서는 Alice*100 이상의 완전한 블록이 ELITE_BLOCK_PROBABILITY에 따라 elite 또는 normal 100개체가 되는지 확인한다.
  • Alice*4000 표본 반복에서 elite 수가 평균 32개, normal 수가 평균 800개에 수렴하고, 모든 plan의 stackCount 합계가 매번 4,000인지 확인한다.
  • 팀 카드가 같은 구성에 대해 E : <elite 수> | N : <normal 수> 형식으로 표시되는지 확인한다.
  • elite HP/공격력/공격속도/사거리/외형이 상수와 stackCount 계산식에 맞는다.
  • normal 치명타가 기존 즉사가 아닌 설정한 고정 배수 피해로 동작하는지 의도와 다시 대조한다.
  • elite 치명타, 메테오, 냉기 피해가 각각 최대 HP 10%, 40%, 20% 기준으로 계산된다.
  • elite 사망 시 사망 통계가 stackCount만큼 증가한다.
  • 어떤 fighter도 처치로 회복하거나 커지거나 공격/이동 속도 보너스를 얻지 않는다.
  • elite에는 Slime의 spawnMultipliersplitOnDeath가 적용되지 않고, normal fighter에는 기존 trait 동작이 유지되는지 확인한다.
  • elite plan에 선택된 스킨 타입은 항상 FIGHTER.ELITE.TYPE과 일치하고 normal plan은 기존 전체 스킨 풀을 사용하는지 확인한다.
  • 밀집 구역 월드 이펙트 표적 산정은 elite를 stackCount만큼 가중한다.
  • npm run build를 통과시키고 실제 전투에서 normal/elite 양쪽 흐름을 수동 확인한다.