arena/context/arena.md

17 KiB

Update: Fighter LOD Worker

  • ArenaScene now starts a dedicated fighterLodWorker.js job for recurring large-battle LOD candidate selection after the initial forced LOD sync.
  • The worker returns detailed fighter worker ids for either full-arena representatives or all living fighters inside the focused rolling window.
  • ArenaScene maps those ids back to current fighter sprites and then reuses applyFighterLodDetailedSet() for the actual Phaser attach/detach work.
  • Worker errors disable the async path and keep the synchronous LOD resolver as a fallback.

Update: Full Rolling-Window Detail Sprites

  • Zoomed, selected, and spectator large-battle views now promote every living fighter inside the rolling camera window to a detailed Phaser sprite.
  • Full-arena overview remains bounded by the representative sprite budget, so the all-map 8,000-fighter view still stays lightweight.
  • addCameraFighterDetails() no longer receives a detail cap; it adds exact viewport candidates first, then all remaining rolling-window candidates.
  • Fighters outside the rolling window stay detached and continue to render as LOD dots.

Update: Async Aggregate Result Safety

  • Worker aggregate results are applied only to models that remain detached from fighterByModelId; if the camera has promoted a fighter to detailed sprite while a worker job is in flight, that result is ignored for the promoted model.
  • Match id checks discard stale worker results after match reset, keeping async aggregate ticks from mutating a new match.

Update: LOD Diff And Dot Frustum Culling

  • Rolling-window LOD now does a one-time full sprite visibility sync when large-battle LOD first activates, then applies only the delta between the previous and next detailed fighter sets on later refreshes.
  • Destroyed fighters are removed from fighterLodDetailedSet, and stale no-scene references are skipped during the diff pass before Phaser input/body APIs are touched.
  • Hidden-fighter dot redraws still use model positions, but skip fighters outside the main camera world view plus PERFORMANCE.LARGE_BATTLE_SPRITE_VIEW_PADDING, avoiding offscreen Graphics.fillRect() calls during zoomed views.
  • Full-arena overview still draws the arena-wide dot field because the main camera view covers the full battlefield at CAMERA.MIN_ZOOM.

Update: Squad Materialization Bridge

  • The arena still keeps individual fighter models for rendering, selection, minimap dots, and deterministic re-entry, but offscreen movement/combat is now driven by squad centers.
  • Aggregate ticks reslot squad members around their squad center, allowing rolling-window LOD to reattach sprites from plausible positions when the camera approaches.
  • Visible attached fighters remain the high-fidelity path; offscreen detached fighters no longer consume individual AI buckets while squad aggregation is active.

Update: Aggregate Detached Simulation Path

  • During large live battles, ArenaScene.updateFighterModels() now calls updateAggregateDetachedCombat() before per-model updates.
  • If aggregate combat is active, only attached/detail fighters continue through full updateFighterModel() every frame; detached fighters are skipped by the individual simulation buckets.
  • This keeps the rolling-window camera area high-fidelity while offscreen fighters continue moving, taking damage, dying, splitting, and changing match outcome through model data.
  • If aggregate combat finishes the match during a batch, updateFighterModels() exits immediately so no stale attached updates run after finishMatch().

Update: Large Battle Simulation Throttle

  • ArenaScene.updateFighterModels() now keeps attached/detailed render models on every-frame updates, but distributes detached model-only fighters across simulation buckets during large live matches.
  • PERFORMANCE.LARGE_BATTLE_SIMULATION_BUCKETS controls the bucket count and LARGE_BATTLE_SIMULATION_MAX_DELTA_MS caps the accumulated delta passed to a skipped detached model.
  • The detailed sprite cap was reduced aggressively for 8,000-fighter battles, and rolling-window detail budgeting now accepts ratios below 1 so dense zoomed views do not promote hundreds-to-thousands of sprites at once.
  • Large-battle fighter HUD health bars use PERFORMANCE.LARGE_BATTLE_HUD_VISIBLE_LIMIT instead of the normal HUD limit.

Update: Fighter Sprite Render Recovery

  • startMatch() still passes attachSprite: false for large live matches, but createFighter() now creates a real Phaser Sprite and immediately parks it instead of returning a pure proxy.
  • This restores visible rendering for normal matches and keeps rolling-window LOD's display/update-list detach path for large battles.
  • spawnSplitFighters() follows the same rule during active large-battle LOD: split children are registered with models and can be parked until LOD promotion.
  • Input events now work directly with Phaser sprites again; _fighterProxy fallback remains harmless for any future proxy experiment.

Update: Render Sprite Detach In Rolling LOD

  • applyFighterLodDetailedSet() treats the detailed set as the list of fighter sprites that should be attached to Phaser for this camera window.
  • Non-detailed living fighters call setFighterDetailVisible(false), which parks the sprite outside the display/update lists and removes its fighterByModelId mapping while keeping the model registered.
  • Detailed fighters are reattached with ensureFighterSpriteAttached() / setFighterSpriteAttached(), so team-button selection can force a detached sprite back before the camera transition and HUD sync.
  • removeDetachedFighterProxyForModel() still removes parked fighter entries after model-only death so dead detached entries do not remain in large-battle scan arrays.
  • LOD candidate collection and minimap dots intentionally scan this.fighters instead of combatTargetIndex.livingFighters, because the combat index's sprite list may contain only currently attached render sprites.

Update: Fighter Model Indexes

  • ArenaScene now keeps fighterModels, fighterByModelId, and fighterModelById in sync with the sprite list.
  • New fighters are registered when a match starts or split-on-death children spawn; despawned or model-only dead fighters are unregistered and their models are marked inactive.
  • fighterModelForId() covers all living models, while fighterForModelId() now returns only currently attached render sprites.
  • unregisterFighterModel() supports model-only cleanup paths that do not have an attached Phaser sprite.

Update: FighterModel Use In Arena LOD

  • Split-on-death spawn origins now use fighterModelPoint(source) so dormant parents spawn children from their simulation position.
  • Rolling-window LOD candidate collection, dot drawing, and minimap fighter dots now read model x/y through fighterModelPoint() instead of direct sprite coordinates.
  • This keeps the large-battle camera/render UI aligned with the simulation model while offscreen render sprites are detached.

Update: Fighter Adapter Use In Arena

  • ArenaScene.finishMatch() stops fighters through fighterAdapter.stopFighterMovement() instead of directly touching Arcade bodies.
  • arenaSpectatorCamera.js uses fighterWorldPoint() and fighterDistanceSquared() so spectator targets, observed combat centers, and closest-pair lookup remain correct when rolling-window LOD has disabled offscreen fighter bodies.
  • Camera/focus code should continue using fighterCameraPoint() or adapter position helpers instead of reading fighter.body.center directly.

Update: Rolling Window Fighter LOD

  • collectCameraFighterDetails() now builds two candidate lists from a camera-centered rolling window: exact viewport candidates and rolling-window candidates.
  • The rolling window is larger than the visible camera view, using PERFORMANCE.LARGE_BATTLE_ROLLING_WINDOW_SCALE plus LARGE_BATTLE_SPRITE_VIEW_PADDING as a minimum expansion.
  • Nearby soon-to-enter fighters remain detailed sprites instead of dots because the focused-camera path now consumes the full rolling-window candidate list.
  • addCameraFighterDetails() still fills exact viewport candidates first, then rolling-window candidates, preserving visible fidelity during camera movement.
  • Fighters outside the detailed set become dormant through setFighterDetailVisible(false), reducing animation/body work while keeping combat simulation active.

Update: Manual Camera Pan/Zoom Tween

  • ArenaScene.transitionMainCameraTo() wraps Phaser camera pan() and zoomTo() for short manual focus transitions.
  • selectFighter() uses the transition helper for scoreboard/team/fighter focus instead of an instant setZoom() plus centerOn().
  • returnToFullArenaView() uses the same helper to move back to arena center at CAMERA.MIN_ZOOM.
  • focusSelectedFighter() skips immediate recentering while the camera pan/zoom effect is active, preventing the selected-fighter follow path from cancelling the transition.

Update: Large Battle Start Camera

  • startMatch() now calls focusLargeBattleStartCamera() after creating live fighters and before the initial LOD sync.
  • Large live matches start at CAMERA.LARGE_BATTLE_START_ZOOM centered on the living fighter nearest to the living population average, so the first view is a readable local battle view instead of the full minimap-like arena.
  • The start camera does not mark a fighter selected; scoreboard team toggle and manual team selection keep their existing behavior.

Update: Team Button Toggle To Full Arena

  • selectRandomTeamFighter() treats a scoreboard click on the already selected team as a toggle-off action instead of choosing another random fighter from that team.
  • returnToFullArenaView() clears selection/focus state, sets CAMERA.MIN_ZOOM, centers on the arena, refreshes the minimap, and updates the scoreboard so the focused team style is removed.

Update: Dynamic Zoomed Fighter LOD

  • Zoomed large-battle LOD now separates exact camera-visible fighters from rolling-window fighters.
  • Focused large-battle LOD promotes the selected fighter plus all living fighters inside the rolling window, reducing the awkward mix of detailed sprites and dots inside the player's current view.
  • addCameraFighterDetails() always consumes exact viewport candidates before rolling-window candidates.
  • Full-arena CAMERA.MIN_ZOOM overview keeps the lower representative budget so the expensive case remains protected.

Update: Large Battle Fighter Render LOD

  • ArenaScene owns the large-battle fighter render LOD pass through syncFighterRenderLod(), resolveFighterLodDetailedSet(), and drawFighterLodDots().
  • The LOD pass activates only for live matches above PERFORMANCE.LARGE_BATTLE_FIGHTER_THRESHOLD; presentation mode and finished matches restore normal fighter visibility.
  • Full-arena overview keeps a bounded set of representative sprites from each team, while zoomed or selected-camera views keep the selected fighter plus camera-near fighters.
  • Hidden living fighters are still present in this.fighters with model state, but their Phaser sprite is removed from the display/update lists and they are drawn as team-colored dots on a shared Graphics object.
  • HUD candidate selection ignores hidden fighters, and match finish disables LOD before post-match handling.

Update: Full-Arena Camera At Lower Render Resolution

  • The Phaser canvas resolution is no longer tied to ARENA.SIZE; CAMERA.MIN_ZOOM is below 1 so the main camera can still frame the full 3200px arena inside the smaller render canvas.
  • Existing team click, selected fighter, meteor focus, and final-combat camera zooms remain absolute zoom targets above that full-arena minimum.

Update: Minimap Redraw Throttle

  • ArenaScene.updateMinimap() accepts a forced refresh flag and otherwise redraws no more often than PERFORMANCE.MINIMAP_REFRESH_MS.
  • Match setup and camera zoom changes force an immediate minimap refresh, while routine scene updates share the throttled path to reduce Graphics redraw work in large battles.

Update: Graphics Minimap And HUD Candidates

  • The minimap is drawn during live matches by ArenaScene as a lightweight Graphics overlay through a dedicated minimap-hud camera instead of reusing the field camera. Presentation/waiting mode hides it.
  • The main camera ignores the minimap graphics object, and the HUD camera ignores field objects as they are added to the scene. Minimap rendering uses team-colored living fighter dots and the main camera viewport rectangle, so the minimap frame stays fixed while the main camera follows combat or meteor focus.
  • ArenaScene refreshes HUD candidates on an interval, choosing selected fighters plus nearby visible fighters when zoomed in, then releases unused HUD pool slots.

Context: Arena & Scene

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

  • ArenaScene.js: Phaser 씬의 생명주기와 전반적인 오케스트레이션을 담당합니다. update() 매 프레임마다 전투원 상태를 체크하고, 카메라 이동 및 UI 모듈 호출을 조율합니다.
  • arenaRenderer.js: 아레나 배경 그래픽, 타일 및 팀별 스타팅 영역 오버레이 렌더링을 담당합니다.
  • arenaSpectatorCamera.js: 관전 모드 시점 계산 및 카메라 포커싱 로직을 담당합니다. 생존 인원에 따른 지능형 카메라 추적 알고리즘이 구현되어 있습니다.

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

지능형 카메라 추적 (Lerp & Jittering 방지)

카메라가 소수점 단위의 평균 좌표를 즉시 따라가면 화면이 떨려 보일 수 있습니다. 이를 방지하기 위해:

  1. 목표 좌표(targetX, targetY)를 Math.round()로 정수화합니다.
  2. 현재 카메라 위치에서 목표 지점까지 매 프레임 0.1의 배율로 거리를 좁혀나가는 Lerp 연산을 수행합니다.
this.cameras.main.scrollX += (targetX - this.cameras.main.midPoint.x) * CAMERA.SPECTATOR_LERP;

자동 관전은 월드 이펙트 임시 시점, 후반 진입과 최종교전 세부 포커싱으로 나뉩니다.

  • 메테오 임시 포커싱: 자동 관전 진입 전 화염 또는 냉기 포격이 시작되면 가장 밀집한 큰 경고 구역의 중심을 임시로 확대 추적합니다. 큰 경고 표시 자체는 WORLD_EFFECT.WARNING_DURATION_MS 이후 사라지며, 카메라는 내부 소형 탄착들이 종료된 뒤 CAMERA.METEOR_FOCUS_HOLD_DURATION만큼 유지한 다음 이전 시점으로 복귀합니다. CAMERA.METEOR_FOCUS_ENABLEDfalse로 설정하면 끌 수 있으며, 수동 선택 시점과 아래 자동 관전 시점이 우선합니다.
  • 후반 자동 관전 진입: 생존 캐릭터가 30명 미만(CAMERA.SPECTATOR_LATE_FIGHTER_THRESHOLD)이 되면 교전 중심(가장 가까운 적 대항쌍)을 포커싱하는 후반 줌을 적용합니다. (2팀만 남았더라도 인원이 많으면 어지러움을 방지하기 위해 자동 관전으로 바로 진입하지 않습니다.)
  • 생존 4명 이하: CAMERA.SPECTATOR_RANDOM_FOCUS_INTERVAL마다 생존 캐릭터 중 한 명을 무작위로 포커싱합니다.
  • 2팀 잔여 & 합계 8명 이하: 더 적은 생존 수를 가진 팀의 중앙을 포커싱하며, 동률이면 기존 교전쌍 중심 포커싱으로 되돌아갑니다.

미니맵 가이드라인

미니맵은 전장 전체를 축소하여 보여주는 독립된 카메라입니다. 주 카메라가 비추는 영역을 계산하여 미니맵 위에 사각형(graphics)을 그려줍니다.

  • camera.displayWidth / zoom 등을 이용하여 현재 월드에서 보이는 실제 영역 크기를 계산합니다.
  • 뷰포트 사각형 좌표는 미니맵 픽셀 격자에 맞춰 반올림하고, 외곽 stroke가 겹쳐 검게 깨지지 않도록 노란 내부 선을 채운 직사각형으로 렌더링합니다.

스타팅 영역 오버레이

스타팅 지점 배치 매치에서는 matchSetup.js가 전장 그리드에서 팀별 중심 셀을 무작위로 뽑아 만든 영역을 ArenaScenearenaRenderer.js에 전달합니다. 렌더러는 각 팀 색상을 낮은 투명도로 채우고 얇게 둘러 실제 스폰 후보 영역을 표시하며, 이 오버레이는 매치 시작 후 5초 동안만 보입니다. 숨김 예약은 Phaser 씬 타이머를 사용하므로 일시정지 시간은 표시 시간에 포함되지 않고, 새 매치가 시작되면 이전 예약을 취소합니다.

씬 상태 관리

  • 프리뷰 모드 (presentationMode): 최초 로드 시 조용히 실행되는 배경 전투입니다. 로컬 저장 옵션과 무관하게 10팀 x 5명 고정 규모로 동작합니다.
  • 일시정지 (setPaused): 실제 전투에서 물리, Phaser 타이머, tween, 스프라이트 애니메이션을 함께 제어합니다. 프리뷰 및 종료된 전투는 제외됩니다.
  • 월드 이펙트 주기: 실제 전투 생성 시 startWorldEffects()를 시작하고, 첫 포격은 WORLD_EFFECT.INTERVAL, 이후 일반 포격은 WORLD_EFFECT.REPEAT_INTERVAL을 사용합니다. 새 매치/종료 때 clearWorldEffects()로 주기 타이머, 잔여 냉각 구역, 메테오 임시 포커스, 캐릭터 감속 배율을 정리합니다. Phaser 타이머를 사용하므로 일시정지 시간은 이 간격과 냉각 지속시간에 포함되지 않습니다.