arena/context/combat.md

19 KiB

Update: Aggregate Combat Worker Path

  • Large-battle detached aggregate combat now tries to run through src/game/combat/aggregateCombatWorker.js before falling back to the synchronous aggregate path.
  • The main thread sends Transferable TypedArrays for detached model ids, position, HP, team key, movement speed, DPS, and frost state; Phaser objects and team/skin references stay on the main thread.
  • Worker results are applied only when the match id still matches and the model is still detached, preventing stale async results from overwriting visible/detail fighters.
  • Stale worker ids whose models have already been unregistered are skipped before reading model fields.
  • The main thread still performs killFighterModel() for worker-reported deaths so split-on-death, kill rewards, death stats, scoreboard updates, and match completion stay on the existing authoritative path.

Update: Magic Attack Effect Pooling

  • spawnSpellEffect() now acquires instant-spell visual sprites from a small per-texture pool and returns them when their attack animation completes.
  • Pooled spell effects are reset on reuse for texture frame, position, scale, depth, alpha, rotation, flip, active/visible state, and animation-complete listeners.
  • clearCombatObjects() now disposes through disposeCombatObject(), allowing active pooled spell effects to be returned during match cleanup while non-pooled projectiles, labels, heal effects, and world effects keep their destroy path.
  • Only magic/instant-spell visuals were pooled here; projectile hit objects and meteor/world-effect objects remain on their existing lifecycle.

Update: Squad-Based Detached Combat

  • Large-battle detached models are grouped into transient squads by arena cell, team id, and PERFORMANCE.LARGE_BATTLE_AGGREGATE_SQUAD_SIZE.
  • Squad AI does nearest-opposing-squad movement and group DPS resolution, then writes surviving members back into deterministic spiral slots around the squad center.
  • This removes per-frame movement/target AI for thousands of offscreen models; individual updateFighterModel() stays reserved for attached/detail fighters in the rolling camera window.
  • In large battles the combat target spatial index is built from attached/detail models, not the full model list, so visible individual AI no longer reintroduces an 8,000-model target scan.

Update: Aggregate Detached Combat

  • updateAggregateDetachedCombat() handles large-battle detached model-only fighters as coarse cell groups instead of invoking full updateFighterModel() AI for each offscreen model.
  • Every-frame work for detached models is now simple movement toward the nearest enemy aggregate cell; target scanning, attack windup, projectile scheduling, and animation locks are reserved for attached/detail fighters.
  • Aggregate damage is computed from group attack DPS on a throttled interval and applied to real FighterModel HP, so deaths, kill rewards, split-on-death, death stats, and winner checks remain tied to the existing combat state.
  • Aggregate kills pass silentLog: true to the model death path to avoid large offscreen death batches flooding the DOM kill log.

Update: Large Battle Combat Frame Throttles

  • prepareCombatFrame() now syncs model positions from fighterByModelId only, so detached/offscreen sprite records are not scanned just to no-op position sync.
  • Large battles reuse the target spatial index for PERFORMANCE.LARGE_BATTLE_TARGET_INDEX_REFRESH_MS instead of rebuilding the full 8,000-model grid every frame.
  • The defensive model-index audit now runs once per second instead of every frame.

Update: Null-Safe Model Target Cache

  • resolveTargetEnemyModel() now clears stale targetModelId values when the cached model can no longer be resolved or is no longer a living enemy.
  • isValidEnemyTargetModel() now null-checks both attacker and candidate models before reading team ids, preventing a removed/dead cached target from crashing the update loop.

Update: Combat With Detached Render Sprites

  • prepareCombatFrame() now syncs model position only from attached sprites; detached sprites are skipped so model-only movement remains authoritative.
  • fighterForModelId() may now return null for living fighters outside the rolling-window detail set, which intentionally routes movement, attacks, damage, and death through the model-only fallback.
  • The target spatial index still builds from scene.fighterModels; its livingFighters compatibility list now represents attached render sprites only, while livingModels remains the full combat list.
  • Model-only death asks ArenaScene.removeDetachedFighterProxyForModel() to remove the parked fighter entry, and livingFighterProxyCount() prevents any remaining dead entries from being re-registered.

Update: Model-Only Combat Fallback

  • updateFighterModel() no longer requires an attached Phaser sprite to keep a living fighter model moving and fighting.
  • If a render sprite exists, movement, animation, projectiles, and death presentation keep using the existing Sprite/Arcade path.
  • If no render sprite exists, movement updates model x/y directly, attacks schedule delayed model hits, damage writes to model HP, and death unregisters the model from ArenaScene indexes immediately.
  • Projectile and instant-spell model-only attacks preserve windup/effect/travel timing, but skip visual projectile/spell objects.
  • Kill reward and split-on-death can now run from model state, so offscreen sprite detachment does not stop combat resolution.

Update: Model-Based Targeting And Spatial Index

  • ArenaScene.update() now iterates scene.fighterModels and calls updateFighterModel() instead of driving combat directly from the sprite array.
  • prepareCombatFrame() still syncs active sprite positions into models, but the target spatial index is built from model records and stores model entries in each grid cell.
  • Target caching moved to model.targetModelId; validation checks model liveness and team identity before resolving the render sprite through scene.fighterForModelId().
  • combatTargetIndex now exposes livingModels as the primary model list while keeping livingFighters as an attached-sprite compatibility list.
  • Attack execution, animation, projectiles, and HUD-facing effects still use sprites when they exist; detached participants resolve through the model-only path.

Update: FighterModel Position Sync In Combat

  • prepareCombatFrame() now syncs each sprite's current render position into its FighterModel before building the target spatial index.
  • Dormant/offscreen fighters keep advancing model x/y through fighterAdapter.moveFighterToward() while visible fighters continue to use Arcade movement and sync back into the model on the next combat frame.
  • Target-grid cell placement and nearest-enemy lookup use model position helpers, keeping the combat path ready for a future model-first update loop.
  • Attack execution still resolves a fighter sprite from targetModelId for movement, animation, and hit visuals. Removing that render dependency is a later migration step.

Update: Fighter Adapter In Combat

  • combat.js no longer owns fighter render/body helpers locally. It imports fighter position, distance, movement, detail visibility, animation, body-disable, and arena-clamp helpers from fighterAdapter.js.
  • Visible fighters still move through Arcade physics, while dormant fighters are advanced by the adapter with JS x/y math and arena clamping.
  • Target selection and camera/world-effect hit points now use adapter position helpers so disabled Arcade bodies do not leave stale centers behind.
  • Ranged attacks still render projectiles only when both attacker and defender are detailed; dormant participation resolves through delayed data hits.

Update: Dormant Fighter Combat Simulation

  • updateFighterModel() accepts delta and manually advances dormant fighters with disabled Arcade bodies using JS position math.
  • Visible fighters still use scene.physics.moveToObject() so nearby/on-screen motion keeps the existing Arcade movement behavior.
  • Attack/hurt animation locks are applied only to detailed fighters. Dormant fighters rely on cooldowns and delayed hit timers instead of animation-complete events.
  • Projectile attacks involving dormant fighters resolve as delayed data hits and skip Phaser projectile object creation.
  • Hit-point, camera, and world-effect helpers treat disabled bodies as stale and use fighter x/y instead.

Update: Projectile And Target Grid Optimization

  • Projectile hit detection now relies on projectilePathHitsDefender() only; it no longer creates one Arcade overlap collider per projectile because the path check already covers fast projectile travel against the defender hit area.
  • Projectile path/hit-area geometry is reused through module-level scratch objects to avoid repeated Line/Rectangle allocation during projectile updates.
  • The per-frame target spatial index now stores cells in a numeric array, avoiding string cell keys and Map writes during every combat frame.
  • clearCombatObjects() also clears scene.combatTargetIndex so match resets and LOD passes do not briefly reuse stale living-fighter lists.

Update: Dense-Area Meteor Barrage

  • worldEffects.js aggregates living fighters on the arena tile grid and uses a summed-area scan to select the WORLD_EFFECT.AREA_TILES square with the highest population.
  • That selected square is a warning/focus area. Each fire or frost event schedules WORLD_EFFECT.IMPACT_COUNT_MIN to IMPACT_COUNT_MAX smaller strikes inside it.
  • Tune the large warning visibility with WORLD_EFFECT.WARNING_DURATION_MS, actual damage/frost footprints with WORLD_EFFECT.IMPACT_AREA_TILES, sprite size with WORLD_EFFECT.IMPACT_VISUAL_SCALE, strike spacing with WORLD_EFFECT.IMPACT_STAGGER_MS, and per-strike variation with WORLD_EFFECT.SIZE_SCALE_VARIANCE.
  • WORLD_EFFECT.INTERVAL sets the delay before the first barrage; subsequent normal barrages use WORLD_EFFECT.REPEAT_INTERVAL, with SUDDEN_DEATH.INTERVAL_MS taking over once sudden death is active.
  • Meteor impact shake uses the same size multiplier, scaling from WORLD_EFFECT.METEOR_SHAKE_DURATION_MS and WORLD_EFFECT.METEOR_SHAKE_INTENSITY.

Update: Large Battle Targeting

  • combat.js now prepares a per-frame target spatial index through prepareCombatFrame(scene).
  • resolveTargetEnemy() keeps valid cached targets until their scan interval expires, then immediately looks up a fresh nearest enemy through the spatial grid.
  • Nearest enemy lookup searches grid cells outward from the fighter's current cell, with full-array scanning kept only as a fallback when no frame index exists.
  • Large-battle corpse cleanup uses PERFORMANCE.LARGE_BATTLE_FIGHTER_THRESHOLD and PERFORMANCE.LARGE_BATTLE_DEAD_DESPAWN_DELAY_MS from src/constants.js.

Update: Team Shadow Animations And Frost Tint

  • Dead fighters keep their death animation/corpse state at initial opacity, then fade out until combat.js removes them from scene.fighters and destroys the sprite.
  • Tune that fade/despawn lifetime with FIGHTER.DEAD_DESPAWN_DELAY_MS and the final alpha with FIGHTER.DEAD_DESPAWN_ALPHA in src/constants.js.
  • Fighter action playback now goes through fighterAdapter.playFighterAction() / playFighterActionIfNeeded(), which resolve animation keys with ensureFighterTeamAnimation() so every action can use the team-shadow baked texture generated from the original spritesheet.
  • The adapter compares against the team-shadow animation key before replaying an action. This avoids switching back to the original non-team-colored spritesheet when fighters move, attack, take damage, or die.
  • Frost stun remains a body tint effect in worldEffects.js. Since team identity is baked into the floor shadow pixels, there is no teamMarker tint state to update or restore.
  • The removed teamMarker display object means death handling no longer needs to hide or destroy a separate marker. HUD cleanup only owns pooled health-bar objects.

Update: Focused Combat Effects In Large Battles

  • combat.js exposes supplemental combat visuals only while a large battle is inside the temporary meteor camera-focus window.
  • Outside that window, large battles skip critical labels, instant-spell sprites, kill-heal sprites, and kill-growth tweens while retaining the underlying damage and reward calculations.
  • World-effect meteor/frost visuals remain visible, and projectile objects remain enabled because projectiles currently participate in hit detection.
  • Projectile objects should keep calling projectilePathHitsDefender() for collision checks instead of adding per-projectile Arcade overlap colliders.

Context: Combat System

1. 모듈별 상세 역할 (src/game/combat/)

  • combat.js: 전투 AI, 피해 계산, 처치 보상 등 핵심 전투 로직을 담당합니다. fighterStats.js에서 해석한 역할별 수치로 이동, 공격, 투사체 발사 등을 처리합니다.
  • combatSettings.js: 전투 속도 배율 등 런타임 전투 설정을 관리합니다.
  • arenaFinalCombatEffects.js: 최종 교전 시 슬로우 모션 등 연출 효과를 담당합니다. 수학적인 이징(easing) 함수와 물리 시간 배율 계산을 포함합니다.
  • worldEffects.js: 실제 전투에서 설정 주기마다 생존자 밀집 구역을 탐색하고 화염/냉기 소형 메테오 포격을 실행하며, 대각선 낙하 연출, 개별 탄착 판정, 냉기 동결과 감속 구역 수명주기를 처리합니다.

2. 주요 로직 구현 세부 사항

전투 AI 및 유닛 동작

  • updateFighterModel(): 가장 가까운 적 모델을 찾아 이동하거나 공격하는 유닛 AI의 핵심입니다.
  • applyHit(): 일반 공격 피해량은 공격자의 melee/ranged/magic 프로필 피해량 범위에서 계산하고, 치명타 적중은 Critical! 표기와 즉시 처치를 처리합니다.
  • 역할별 기본값: src/constants.jsFIGHTER_TYPE_STATS에서 체력, 이동속도, 사거리, 공격 쿨다운, 피해량, 치명타 확률, 발동 지연을 독립적으로 조절합니다. 투사체 속도는 ranged, 효과 적중 지연은 magic 프로필에 포함됩니다.
  • projectilePathHitsDefender(): 투사체가 대상을 스쳐 지나가지 않도록 궤적(Line)과 히트박스(Rectangle) 겹침 검사를 수행합니다.

처치 보상 및 성장

  • applyKillReward(): 처치한 캐릭터의 체력 회복(현재 체력 30%), 크기 증가, 공격속도/이동속도 배율 증가를 처리합니다. 누적 배율은 KILL_GROWTH_MAX_MULTIPLIER로 제한합니다.
  • clampFighterInsideArena(): 처치 성장 중 커진 캐릭터가 전장 바깥으로 나가지 않도록 위치를 보정합니다.

월드 이펙트

  • 발동 규칙: 프리뷰가 아닌 실제 전투에서 시작 후 첫 포격은 WORLD_EFFECT.INTERVAL이 지난 뒤 발생하고, 이후 일반 포격은 WORLD_EFFECT.REPEAT_INTERVAL 간격으로 발생합니다. 각 포격은 AREA_TILES 크기의 모든 후보 구역을 타일 누적합으로 평가해, 생존 캐릭터가 가장 많이 모인 범위를 선택합니다. 같은 밀도의 후보가 여러 개일 때만 그 후보 사이에서 무작위로 고릅니다.
  • 포격 판정: 선택된 큰 범위는 경고 표시와 카메라 포커스 대상으로 사용되고, 내부에 투하되는 작은 탄착 영역만 피해, 기절, 냉기 감속을 처리합니다. 이 때문에 넓은 밀집지대를 위협하면서도 영역 전체를 즉시 동일 피해로 덮지 않습니다.
  • 서든 데스 (Sudden Death):
    • 조건: 매치 시작 후 WORLD_EFFECT.SUDDEN_DEATH.TRIGGER_MS 시간이 경과하면 서든 데스 상태에 진입합니다 (활성화 시).
    • 효과: 메테오 투하 주기가 SUDDEN_DEATH.INTERVAL_MS로 단축되며, FORCE_FROST 설정 시 빙결 효과를 가진 냉기 메테오가 집중적으로 생성됩니다.
    • 목적: 장기전을 방지하고 전장에 무작위 변수를 극대화하여 물량 중심 팀에게 리스크를 부여합니다.
  • 낙하 방향과 크기: 대상이 전장 좌측 반면(2, 3사분면)이면 화살표가 좌상단에서 우하단으로, 우측 반면(1, 4사분면)이면 좌우 반전되어 우상단에서 좌하단으로 이동합니다. 스프라이트를 45도로 기울이고 전용 시각 배율을 사용해 전역 마법 규모로 표현합니다.
  • 화염 메테오: world_Effect.png의 소형 탄착 애니메이션 3~4개가 밀집 경고 구역 내부로 순차 낙하합니다. 각 탄착은 크기에 따른 화면 흔들림과 개별 영역 고정 피해를 적용합니다. 환경 피해로 인한 사망은 킬 보상을 지급하지 않지만 사망 통계와 승패 판정에는 반영됩니다.
  • 냉기 메테오: world_Effect_2.png의 소형 탄착 애니메이션들이 같은 방식으로 낙하합니다. 개별 탄착 피해를 입고 생존한 대상은 얼음색으로 바뀐 채 설정 시간 동안 기절하며, 각 탄착점에 남는 냉각지대 안에서는 공격속도와 이동속도 감속 배율을 적용합니다.

최종교전 슬로우모션

COMBAT.FINAL_SLOW_MOTION_ENABLED가 활성화된 경우:

  • 최종교전 상태에서 공격 모션이 시작될 때 전역 time scale을 낮춥니다.
  • 진입/유지/복귀 속도 램프(Ease)를 적용합니다.
  • Arcade Physics는 timeScale 방향이 반대라 물리 이동에는 역수 배율을 적용합니다.

3. 유지보수 규칙

  • 처치 성장 상한: src/constants.jsKILL_GROWTH_MAX_MULTIPLIER를 수정합니다.
  • 공격력 조정: src/constants.jsFIGHTER_TYPE_STATS.<type>.damageMin/damageMax를 수정합니다.
  • 월드 이펙트 및 서든 데스 조정:
    • src/constants.jsWORLD_EFFECT.METEOR_DAMAGEWORLD_EFFECT.FROST_DAMAGE로 피해량을 조정합니다.
    • SUDDEN_DEATH.ENABLED로 서든 데스 활성화 여부를 결정하며, TRIGGER_MS(시작 시간), INTERVAL_MS(주기), FORCE_FROST(냉기 고정) 설정을 변경할 수 있습니다.
    • INTERVAL은 첫 포격까지의 대기 시간, REPEAT_INTERVAL은 이후 일반 포격 주기입니다. AREA_TILES는 밀집도를 검색하고 경고로 표시할 큰 구역이며, WARNING_DURATION_MS는 그 경고 표시 시간입니다. IMPACT_AREA_TILES, IMPACT_COUNT_MIN/IMPACT_COUNT_MAX, IMPACT_STAGGER_MS, IMPACT_VISUAL_SCALE는 내부 소형 포격의 판정 범위, 발수, 간격, 시각 크기를 조정합니다.
    • WORLD_EFFECT.FROST_STUN_DURATION/FROST_STUN_TINT로 동결 시간과 표시 색상을 조정합니다.
    • 나머지 WORLD_EFFECT.* 값으로 발동 주기, 범위, 냉각 지속시간과 감속 정도를 수정하며, 메테오 착탄 위치 포커싱은 CAMERA.METEOR_FOCUS_ENABLED에서 켜고 끕니다.
  • 특수 규칙: 캐릭터별 특수 공격 방식은 fighterManifest.jscombat 설정을 확인합니다.