# Update: Parked Body Removal From Arcade World - `disableFighterBody()` now calls `scene.physics.world.disable(fighter)` for parked or dead fighter sprites, removing the body from Arcade World's active body set instead of only setting `body.enable = false`. - `enableFighterBody()` re-adds the sprite body with `world.enable(fighter)`, resets it to the model position, stops residual velocity, and syncs the model from the reattached sprite. - `setFighterDetailVisible()` uses these adapter helpers for LOD detach/reattach, so render parking also removes offscreen bodies from physics traversal. - `isLivingFighterModel()` now returns false for missing/null models so stale async ids and removed models cannot pass living checks. # Update: Parked Fighter Detail Early Return - `setFighterDetailVisible(false)` now returns immediately when a fighter is already parked, avoiding repeated body/input/HUD/display-list work during large-battle LOD refreshes. # Update: Detached Fighter Animation Guard - `shouldRenderFighterDetail()` now requires an actual active fighter object before returning true, preventing model-only large-battle combat from trying to animate a `null` sprite. - `playFighterAction()` and `playFighterActionIfNeeded()` now skip playback if no animation key can be resolved. # Update: Fighter Sprite Render Recovery - `createFighter()` returns a real Phaser Sprite again, with combat-facing fields bridged to `fighter.model`. - The lazy `SpriteProxy` pool was rolled back because the proxy handoff could leave the simulation data alive while no stable Phaser render object was visible. - `attachSprite: false` is still accepted for large-battle startup, but it now creates the sprite and immediately parks it through `setFighterDetailVisible(false)` instead of skipping Sprite creation. - Rolling-window LOD still removes non-detailed sprites from Phaser's display/update lists and restores the same sprite from model `x/y` when it becomes detailed again. # Update: Fighter Render Sprite Detach - `setFighterDetailVisible(false)` now parks a fighter sprite by disabling body/input/HUD, pausing animation, hiding it, and removing it from Phaser's display and update lists. - `setFighterDetailVisible(true)` reattaches the same sprite, resets its body from model `x/y`, resumes animation, and restores pointer interaction for living fighters. - `syncFighterModelFromSprite()` ignores detached sprites so offscreen model-only movement cannot be overwritten by a stale parked sprite position. - Adapter helpers treat `_spriteDetached` as non-rendered/non-body state, so animation, body position, and projectile path logic naturally fall back to model data. # Update: FighterModel Shell - `fighterModel.js` now creates the pure JS state record for a fighter. The model owns combat-facing fields including HP, team/skin references, `targetModelId`/cooldown state, selection, lock/death flags, kill-growth state, frost state, detail visibility, facing, and model `x/y`. - `attachFighterModel()` connects a Phaser sprite to its model and preserves the existing `fighter.hp`, `fighter.team`, `fighter.isDead`, etc. surface through getter/setter bridges. This keeps the current code stable while making `fighter.model` the state home. - `isLivingFighterModel()` and `fighterModelDistanceSquared()` support model-first combat code without requiring a Sprite wrapper. - `fighterFactory.js` creates a Phaser Sprite with an attached `fighter.model` bridge. HUD slots, timers, scale, and input hit areas remain render concerns. - `fighterAdapter.js` updates model `x/y` when sprites are synced or when dormant fighters move manually, and now treats detached proxies as model-only for body/render checks. # Update: Fighter Adapter Layer - `fighterAdapter.js` centralizes fighter-facing Phaser operations: `fighterWorldPoint()`, `fighterDistanceSquared()`, `setFighterFacing()`, `moveFighterToward()`, `stopFighterMovement()`, `enableFighterBody()`, `disableFighterBody()`, `clampFighterInsideArena()`, animation playback, and frost tint helpers. - `fighterFactory.js` owns sprite creation plus detail visibility; offscreen fighters remain model-backed while their sprite is parked outside Phaser render/update traversal. - Treat the adapter as the boundary for the upcoming model/proxy split. Code outside `src/game/fighter/` should avoid new direct fighter `body`, `setFlipX()`, `setVelocity()`, or animation calls unless it is explicitly dealing with a non-fighter object. # Update: Dormant Fighter Detail State - `setFighterDetailVisible(false)` now makes a non-detailed fighter dormant and detached from Phaser render/update traversal. - `setFighterDetailVisible(true)` re-enables the body at the model position, resumes animation, and restores pointer interaction for living fighters. - Dormant fighters remain sprite/model records in `this.fighters` so existing match arrays, ownership, death stats, split-on-death, and team bookkeeping remain intact. # Update: Fighter Detail Visibility For LOD - `fighterFactory.js` exposes `setFighterDetailVisible()` so `ArenaScene` can hide or restore the detailed Phaser sprite for large-battle render LOD without removing the fighter from combat simulation. - Hidden fighters release borrowed HUD slots and disable pointer interaction; visible living fighters keep their original hit-area based interaction. - `syncFighterHud()` now treats invisible fighters as HUD-ineligible, preventing hidden LOD fighters from holding health-bar display objects. - Detached LOD fighters now pause animation safely because combat locks and delayed hits can resolve through the model-only fallback while the sprite is parked. # Update: HUD Pooling - `fighterFactory.js` no longer creates permanent HUD objects for every fighter; zoom HUD now shows health bars without fighter name labels. - HUD health-bar display objects are pooled on the scene and assigned only to selected fighters or zoom-visible nearby fighters chosen by `ArenaScene`. - `syncFighterHud()` acquires a slot lazily and `releaseFighterHud()` returns it to the pool when the fighter leaves the HUD candidate set, dies, or is destroyed. - Tune pool size and visible candidate limits in `PERFORMANCE.FIGHTER_HUD_POOL_SIZE` and `PERFORMANCE.FIGHTER_HUD_VISIBLE_LIMIT`. # Update: Team Shadow Sprite Optimization - Team identity is no longer rendered with a duplicated `teamMarker` sprite. `fighterFactory.js` now creates only the main Phaser sprite for each fighter. - `fighterAssets.js` lazily creates team-colored spritesheets for the actual `skin + action + teamColor` combinations used in a match. The derived texture keeps the original character art and replaces only the floor shadow color (`#534545`) in the lower frame band (`y=55..59`) with the team color. - Combat animation playback calls `ensureFighterTeamAnimation()` before switching actions, so idle, walk, attack, hurt, and death states keep the same team shadow treatment. - This reduces display-list cost from `fighter sprite + teamMarker sprite` to a single fighter sprite. The tradeoff is extra texture memory for team-colored derivatives, so derived textures must stay lazy and should not be pre-generated for the whole manifest. - Frost stun still uses the fighter body's `setTint(WORLD_EFFECT.FROST_STUN_TINT)`. Do not reuse tint for team identity; team color is baked into the shadow pixels instead. # Context: Fighter & Assets ## 1. 모듈별 상세 역할 (`src/game/fighter/`) - **`fighterAssets.js`**: 캐릭터 스프라이트 로드 및 애니메이션/실루엣 생성을 담당합니다. 원본 이미지로부터 팀 색상 마커용 실루엣을 동적으로 생성합니다. - **`fighterFactory.js`**: 캐릭터 인스턴스화 및 HUD 체력바 관리를 담당합니다. Phaser Sprite와 DOM UI 사이의 가교 역할을 합니다. - **`fighterManifest.js`**: 모든 캐릭터 종족 및 스탯 데이터를 정의합니다. 20여 종의 캐릭터 설정이 포함되어 있습니다. - **`fighterStats.js`**: 공격 방식으로 `melee`, `ranged`, `magic` 역할을 판별하고 역할별 기본 스탯과 스킨별 오버라이드를 병합합니다. - **`fighterSelection.js`**: 매치 참여 캐릭터를 무작위로 선택하거나 섞는 로직을 담당합니다. ## 2. 주요 로직 구현 세부 사항 ### 동적 팀 실루엣 생성 팀 색상 표시는 캐릭터 모양을 정교하게 따라가는 별도 spritesheet입니다. 1. `fighterAssets.js`가 로드된 원본 스프라이트의 alpha 데이터를 캔버스에서 읽습니다. 2. 원본 alpha 픽셀 주변 `SELECTED_FIGHTER_OUTLINE_GAP`은 비우고, 그 바깥 `SELECTED_FIGHTER_OUTLINE_WIDTH`에만 흰색 outline을 칠합니다. 3. `fighterFactory.js`에서 생성된 캐릭터 뒤에 배치하고 팀 색상으로 tint 처리합니다. 4. 캐릭터가 성장하여 커져도 같은 배율로 실루엣이 유지됩니다. ### 캐릭터 HUD 및 상태 동기화 - **체력바 표시**: 줌 또는 선택 상태에서 후보 fighter만 pooled HUD slot을 빌려 체력바를 표시합니다. 이름표는 zoom HUD에 표시하지 않습니다. - **사망자 처리**: 사망 시 HUD와 팀 마커를 숨겨 화면 가독성을 높입니다. 본체 sprite만 낮은 depth와 반투명 상태로 남깁니다. - **월드 감속 상태**: 생성 시 `worldEffectSpeedMultiplier`를 `1`로 초기화하며, 냉각지대 안에서는 `worldEffects.js`가 해당 배율을 낮춰 공격속도와 이동속도 계산에 반영합니다. - **냉기 동결 상태**: `isFrostStunned`와 동결 타이머를 캐릭터별로 관리합니다. 냉기 메테오 착탄에 생존하면 캐릭터 본체와 팀 실루엣 마커가 함께 얼음색으로 바뀌고, 동결 종료 시 본체 원본 색상과 저장된 팀 색상으로 복구됩니다. ### 캐릭터별 특성 (예: Slime) - **`spawnMultiplier`**: 배정된 슬롯 1개를 지정된 수만큼 확장하여 스폰합니다. - **`splitOnDeath`**: 사망 시 확률적으로 지정된 수만큼 분열체를 생성합니다. - **스탯 상한**: 처치 보상은 현재 체력을 회복시키지만 `maxHp`를 넘을 수 없습니다. (예: Slime은 항상 1 HP) ### 역할별 전투 스탯 - `combat.type`이 `projectile`이면 `ranged`, `instant-spell`이면 `magic`, 그 외에는 `melee` 기본 프로필을 사용합니다. - 새로운 공격 구현이 기본 판별과 다른 역할을 사용해야 할 때는 `combat.fighterType`에 `melee`, `ranged`, `magic` 중 하나를 명시합니다. - 개별 스킨의 기존 `stats.maxHp`, `combat.range`, `combat.cooldown`, `combat.criticalChance`, `combat.projectile.speed`, `combat.attackEffect.hitDelay` 설정은 역할별 기본값보다 우선합니다. ## 3. 유지보수 규칙 - **신규 캐릭터**: 에셋 배치 후 `fighterManifest.js`에 정의를 추가합니다. - **종족값**: 사망 통계를 위해 지정된 6개 종족 중 하나를 반드시 선택해야 합니다. - **캐릭터 특성**: 전투 중 새 유닛을 생성하는 특성은 `ArenaScene.js`의 생성 헬퍼를 통해 `this.fighters`와 `team.size`를 함께 갱신해야 합니다.