arena/agent.md

23 KiB

Agent: Arena Picker

0. 필수

  • 작업이 완료되면 작업과 관련된 모든 문서를 함께 업데이트한다.
  • 대규모 전투, LOD, 모델/렌더 분리, 전투 워커, 서버 API를 수정할 때는 관련 context/ 문서를 먼저 확인하고 변경 내용을 문서에 반영한다.

1. 프로젝트 정의

Arena Picker는 Phaser 3 게임 엔진과 Vite 번들러를 기반으로 구축된 대규모 팀 전투 시뮬레이션 웹 애플리케이션입니다. 사용자가 입력한 여러 참가자 닉네임을 각각 하나의 팀으로 설정하고, 닉네임*N 형식으로 지정된 인원만큼 캐릭터를 생성해 자동 전투를 시뮬레이션합니다.

전장은 3200px 월드 크기를 유지하되 Phaser 내부 렌더 캔버스는 1280px로 낮춰 픽셀 작업량을 줄입니다. 일반 전투는 개별 Phaser Sprite와 Arcade Physics를 사용하고, 3,000명 이상 대규모 전투에서는 FighterModel 중심 시뮬레이션, rolling-window LOD, Web Worker 기반 후보 선정/집계 전투, HUD/이펙트 풀링을 결합해 8,000명급 전투를 처리합니다.

서버 런타임은 Fastify를 사용하며 MongoDB 커넥션 풀을 유지합니다. 방문자 수, 일일 운영 지표, 전투 사망 통계, About 콘텐츠를 API로 제공합니다.

2. 현재 아키텍처 핵심

2.1 FighterModel 기반 상태와 렌더 브리지

  • src/game/fighter/fighterModel.js가 HP, 팀, 스킨, 타깃, 쿨다운, 사망/선택/성장/동결 상태, 모델 좌표를 보관하는 순수 JS 상태 객체를 만듭니다.
  • fighterFactory.js는 실제 Phaser Sprite를 생성하고 fighter.model 브리지로 기존 fighter.hp, fighter.team 스타일 접근을 호환합니다.
  • fighterAdapter.js는 위치, 거리, 방향, 이동, body enable/disable, 애니메이션, 동결 tint, arena clamp 등 Phaser Sprite 접근의 경계입니다. 전투/카메라/월드 이펙트 코드는 새로 직접 body, setVelocity(), 애니메이션 API를 만지기보다 adapter를 우선 사용합니다.
  • ArenaScenefighterModels, fighterByModelId, fighterModelById를 함께 유지합니다. fighterForModelId()는 현재 attach된 렌더 Sprite만 반환할 수 있으므로, 전투 로직은 null 가능성을 항상 고려합니다.

2.2 대규모 전투 렌더 LOD

  • 대규모 live match는 PERFORMANCE.LARGE_BATTLE_FIGHTER_THRESHOLD 이상에서 render LOD를 활성화합니다.
  • full-arena overview는 PERFORMANCE.LARGE_BATTLE_SPRITE_RENDER_LIMIT만큼 팀별 대표 Sprite를 유지하고, 나머지 생존자는 팀 색상 dot으로 표시합니다.
  • zoomed, selected, spectator 시점은 rolling camera window 안의 모든 생존자를 detailed Sprite로 승격합니다. 이 경로는 더 이상 별도 zoom cap이나 buffer ratio에 묶이지 않습니다.
  • src/game/arena/fighterLodWorker.js는 생존 fighter worker id, position, team key TypedArray를 받아 현재 match/job의 detailed id 목록만 반환합니다. Worker 실패 또는 오류 시 resolveFighterLodDetailedSet() 동기 경로로 fallback합니다.
  • LOD 적용은 최초 활성화 때 full sync를 수행한 뒤, 이후에는 이전 detailed set과 다음 set의 차이만 attach/detach합니다.
  • parked fighter는 display/update list에서 빠지고 Arcade World에서도 world.disable()로 제거됩니다. 재진입 시 world.enable() 후 모델 좌표로 body를 복구합니다.
  • hidden-fighter dot redraw는 zoomed view에서 카메라 viewport와 padding 밖의 dot을 건너뛰어 Graphics.fillRect() 비용을 줄입니다.

2.3 대규모 전투 집계 시뮬레이션

  • attached/detail fighter는 매 프레임 updateFighterModel()로 고정밀 개별 AI를 유지합니다.
  • detached/offscreen fighter는 team + cell + squad 단위로 압축되어 coarse movement와 group DPS를 처리합니다. 기본 squad 크기는 PERFORMANCE.LARGE_BATTLE_AGGREGATE_SQUAD_SIZE가 제어합니다.
  • src/game/combat/aggregateCombatWorker.js는 detached model id, position, HP, team key, 이동속도, DPS, frost flag를 Transferable TypedArray로 받아 집계 전투를 계산합니다.
  • Phaser 상태 변경, death 처리, split-on-death, kill reward, scoreboard, match finish는 여전히 main thread가 소유합니다.
  • Worker 결과는 match id가 일치하고 해당 model이 여전히 detached일 때만 적용합니다. 이미 attach된 fighter, 죽었거나 unregister된 stale id는 무시합니다.
  • Worker 생성 실패 또는 오류 시 기존 동기 집계 전투 경로로 fallback합니다.

2.4 전투 및 이펙트 최적화

  • target spatial index는 model 기반으로 구성하되, 대규모 전투에서는 attached/detail model 중심으로 갱신해 8,000명 전체 스캔을 줄입니다.
  • stale targetModelId는 null-safe validation으로 정리합니다.
  • instant-spell 공격 시각 효과는 texture별 sprite pool을 재사용합니다. clearCombatObjects()는 active pooled effect도 공통 cleanup 경로로 반환합니다.
  • projectile hit detection은 projectile마다 Arcade overlap collider를 만들지 않고 line/rectangle path check와 scratch geometry를 재사용합니다.
  • 대규모 전투에서 critical label, instant-spell sprite, kill-heal sprite, kill-growth tween 같은 보조 효과는 meteor camera focus 중일 때만 노출합니다. damage, heal, 성장 수치 자체는 유지됩니다.
  • world effect는 랜덤 생존자 대신 생존자 밀집도가 가장 높은 tile square를 큰 경고 영역으로 잡고, 내부에 소형 화염/냉기 strike를 분산 투하합니다.

2.5 카메라, HUD, 서버 지표

  • 대규모 live match 시작 시 full-arena 최저 줌 대신 평균 생존 위치에 가까운 fighter 주변으로 CAMERA.LARGE_BATTLE_START_ZOOM을 적용합니다.
  • scoreboard 팀 버튼을 이미 선택된 팀에 다시 클릭하면 선택을 해제하고 full-arena view로 돌아갑니다.
  • 수동 fighter/team focus와 full-arena return은 transitionMainCameraTo()의 Phaser pan()/zoomTo() tween을 사용합니다.
  • HUD 체력바는 모든 fighter가 영구 소유하지 않고 pool에서 빌려 씁니다. selected fighter와 zoom-visible 후보만 slot을 보유하며, zoom HUD에는 fighter 이름을 표시하지 않습니다.
  • live minimap은 별도 HUD camera와 Graphics dot overlay로 렌더링하며 PERFORMANCE.MINIMAP_REFRESH_MS로 redraw를 throttle합니다.
  • 서버는 visitor, death stats, daily metrics, About 콘텐츠 API를 제공합니다.

3. 프로젝트 전체 구조 (Directory Tree)

├── index.html                  # 메인 HTML 진입점 및 UI 레이아웃
├── package.json                # Phaser, Vite, Fastify, MongoDB 의존성 및 npm scripts
├── config.json.sample          # 공유용 서버/MongoDB 설정 예시
├── agent.md                    # 프로젝트 개요 및 에이전트 작업 가이드
├── todo.md                     # 작업 내역 및 잔여 이슈 관리
├── build.sh                    # 배포/빌드 보조 스크립트
├── context/                    # 상세 개발 가이드
│   ├── core.md                 # main.js, constants.js, 렌더/성능 상수, worker entrypoint
│   ├── arena.md                # ArenaScene, camera, minimap, fighter render LOD
│   ├── combat.md               # 전투 AI, model-only combat, aggregate combat, world effects
│   ├── fighter.md              # FighterModel, adapter, factory, HUD pool, team-shadow texture
│   ├── match-ui.md             # 매치 설정, spawn, HUD, kill log, victory UI
│   ├── server.md               # Fastify, MongoDB, visitor/death/daily metrics/About API
│   ├── style.md                # CSS 모듈, 디자인 변수, 반응형/애니메이션 규칙
│   └── refactor/
│       └── arena-scene-modularization-work-order.md
├── server/                     # Fastify API 서버 및 MongoDB 연결 관리
│   ├── index.js                # Fastify 진입점, Vite dev middleware, 정적 배포 서빙
│   ├── config.js               # config.json 로드 및 MongoDB URI/컬렉션 설정
│   ├── db.js                   # MongoClient 커넥션 풀 생성/재사용/종료
│   ├── visitorCookie.js        # 방문자 UUID 쿠키 읽기/쓰기/검증
│   ├── visitors.js             # 유니크 방문자 체크 및 통계 API
│   ├── dailyMetrics.js         # 일일 방문/전투 시작/전투 종료/후원 클릭 지표 API
│   ├── deathStats.js           # 종족별 전투 사망 통계 API
│   └── about.js                # About 개발자정보/개인정보처리방침 seed 및 조회 API
├── public/                     # 정적 리소스
│   └── assets/
│       ├── og-image.png        # 공유 미리보기 이미지
│       ├── effects/
│       │   ├── heal/           # 처치 회복 연출
│       │   ├── world_Effect.png
│       │   └── world_Effect_2.png
│       └── characters/         # 20종 이상 캐릭터 스킨/투사체/마법 이펙트 에셋
│           ├── archer/
│           ├── armored-axeman/
│           ├── armored-orc/
│           ├── priest/
│           ├── wizard/
│           └── ...             # knight, orc, skeleton, slime, wolf, bear 계열 등
└── src/                        # 프론트엔드 소스 root
    ├── main.js                 # Phaser game config, 앱 상태, 옵션 drawer, 방문자 추적
    ├── constants.js            # 렌더/전장/전투/카메라/성능/월드 이펙트 상수
    ├── styles.css              # CSS 모듈 통합 엔트리
    ├── styles/
    │   ├── base.css            # 전역 변수, reset, 기본 레이아웃
    │   ├── intro.css           # 대기 화면 및 프리뷰 스타일
    │   ├── game-ui.css         # scoreboard, kill log, battle notice, victory layer
    │   ├── overlay.css         # option drawer, About dialog, form controls
    │   ├── animations.css      # 공통 keyframes/animation utilities
    │   └── mobile.css          # 960px 이하 반응형 override
    ├── game/
    │   ├── arena/
    │   │   ├── ArenaScene.js           # 메인 Phaser Scene orchestrator
    │   │   ├── arenaRenderer.js        # 전장 바닥, grid, starting zone 렌더링
    │   │   ├── arenaSpectatorCamera.js # 자동/수동 카메라 포커싱
    │   │   └── fighterLodWorker.js     # 대규모 전투 detailed sprite 후보 worker
    │   ├── combat/
    │   │   ├── combat.js               # model 기반 전투 AI, 타깃, 피해, 처치 처리
    │   │   ├── aggregateCombatWorker.js# detached/offscreen 집계 전투 worker
    │   │   ├── combatSettings.js       # 전투 속도 및 이동 배율 설정
    │   │   ├── arenaFinalCombatEffects.js
    │   │   └── worldEffects.js         # 밀집 구역 메테오/냉기/감속/동결 효과
    │   ├── fighter/
    │   │   ├── fighterModel.js         # 순수 JS fighter 상태 모델
    │   │   ├── fighterAdapter.js       # Phaser Sprite/Physics 접근 경계
    │   │   ├── fighterAssets.js        # sprite load, team-shadow texture/animation 생성
    │   │   ├── fighterFactory.js       # Sprite 생성, model bridge, HUD pool, detail visibility
    │   │   ├── fighterManifest.js      # 캐릭터 스탯/종족/특성 정의
    │   │   ├── fighterStats.js         # melee/ranged/magic 프로필 해석
    │   │   └── fighterSelection.js     # 캐릭터 선택/셔플 로직
    │   └── match/
    │       ├── matchSetup.js           # `닉네임*N` 파싱, 팀 구성, spawn 좌표 계산
    │       └── arenaMatchRuntime.js    # match 진행 중 helper
    └── ui/
        ├── matchForm.js        # 설정 폼 및 localStorage 유지
        ├── aboutDialog.js      # About dialog 및 Markdown 표시
        ├── visitorCounter.js   # 방문자 API 호출/표시
        ├── dailyMetrics.js     # 일일 지표 API 호출
        ├── deathStats.js       # 사망 통계 API 호출
        ├── arenaScoreboard.js  # 팀 badge 및 선택 상태
        ├── arenaKillLog.js     # kill log DOM
        ├── battleDeathNotice.js# 상단 사망/통계 안내
        └── victoryCelebration.js

로컬/생성 파일인 config.json, node_modules/, dist/, .vite/, package-lock.json, *.log.gitignore 대상입니다.

4. 상세 기술 가이드 (Context Routing)

토큰 절약 및 효율적인 정보 조회를 위해 상세 로직은 기능별 문서로 분리되어 있습니다. 특정 모듈 작업 시 아래 문서를 먼저 읽으십시오.

  • 인프라 및 전역 설정: main.js, constants.js, 렌더 크기, PERFORMANCE, worker entrypoint, 공통 유지보수 규칙.
  • 아레나 및 카메라: ArenaScene, rolling-window LOD, fighterLodWorker.js, minimap, spectator/manual camera.
  • 전투 엔진: combat.js, model-only combat fallback, target spatial index, aggregateCombatWorker.js, world effects.
  • 캐릭터 및 에셋: FighterModel, fighterAdapter.js, sprite attach/detach, HUD pool, team-shadow texture.
  • 매치 로직 및 UI: 닉네임*N 팀 인원, spawn zone, scoreboard, kill log, victory UI, 모바일 레이아웃.
  • 서버 및 API: Fastify, MongoDB, visitor cookie, daily metrics, death stats, About 콘텐츠.
  • 스타일 및 디자인: CSS 모듈 구조, 디자인 변수, 반응형 및 애니메이션 가이드.

5. 주요 기능 상세

5.1 매치 입력과 스폰

  • live match 참가자는 닉네임*N 형식으로 팀별 배정 인원을 직접 지정합니다. 접미사가 없으면 1명입니다.
  • SPAWN.MAX_FIGHTER_COUNT는 참가자 입력으로 배정되는 fighter 수의 상한입니다. Slime의 spawnMultiplier, splitOnDeath 같은 특성 기반 추가 생성은 이 입력 상한에 포함하지 않습니다.
  • starting-zone placement는 SPAWN.FIGHTERS_PER_STARTING_ZONE마다 팀 영역을 추가로 배정해 대규모 팀이 한 점에 뭉치지 않도록 분산합니다.
  • match-start validation은 요청 인원과 허용 인원을 분리해 사용자에게 경고 카드로 보여줍니다.

5.2 대규모 전투 흐름

  • match 시작 시 ArenaScene은 live fighter 수가 threshold 이상인지 판단하고 large-battle 모드로 들어갑니다.
  • 첫 화면은 full-arena overview가 아니라 living fighter 평균 위치에 가까운 fighter 주변으로 zoom합니다.
  • 최초 LOD sync 후 fighterLodWorker.js 또는 동기 resolver가 현재 카메라 상태에 맞는 detailed set을 계산합니다.
  • full overview는 대표 Sprite와 dot field를 유지합니다. focused view는 rolling window 안의 모든 생존자를 detail Sprite로 복구합니다.
  • offscreen/detached model은 집계 squad combat으로 이동/피해/사망을 처리하고, 카메라에 다시 들어오면 model position에서 Sprite를 재attach합니다.

5.3 모델/렌더 생명주기

  • createFighter()는 항상 실제 Phaser Sprite를 만들고 FighterModel을 붙입니다. 과거 lazy SpriteProxy 실험은 rollback되었습니다.
  • attachSprite: false는 Sprite 생성을 건너뛰는 뜻이 아니라, 생성 직후 setFighterDetailVisible(false)로 parking한다는 뜻입니다.
  • parking된 fighter는 render/update/physics traversal에서 빠지지만 model state는 계속 살아 있습니다.
  • model-only death는 model을 inactive/unregister 처리하고 parked fighter entry를 제거합니다.
  • animation helper는 실제 renderable fighter가 없으면 action key resolution/playback을 건너뜁니다.

5.4 전투, 효과, 월드 이벤트

  • updateFighterModel()은 Sprite가 있으면 기존 Arcade/animation path를 사용하고, Sprite가 없으면 model 좌표/HP/쿨다운 기반으로 이동과 공격을 진행합니다.
  • ranged/magic 공격은 양쪽 Sprite가 모두 있으면 visual projectile/spell path를 사용합니다. detached 참여자가 있으면 같은 windup/travel/hit delay를 model hit로 해석합니다.
  • kill reward, split-on-death, death stats, scoreboard, match finish는 Sprite 유무와 무관하게 기존 authoritative path를 사용합니다.
  • dense-area meteor barrage는 큰 경고 범위를 먼저 표시한 뒤 내부 소형 strike에만 피해/동결/감속을 적용합니다.
  • sudden death는 설정 시간 이후 meteor 주기를 단축하고 필요 시 frost meteor를 강제해 장기전을 방지합니다.

5.5 카메라와 HUD

  • transitionMainCameraTo()는 수동 focus 이동에 Phaser pan()/zoomTo()를 적용합니다.
  • selected fighter auto-centering은 수동 tween 중에는 기다려 tween을 취소하지 않습니다.
  • scoreboard에서 선택된 팀을 다시 클릭하면 selection/focus/meteor focus를 정리하고 full-arena view로 돌아갑니다.
  • minimap은 field camera와 분리된 HUD camera로 고정 표시하며, main camera viewport rectangle과 team-colored dot을 그립니다.
  • fighter HUD는 pool 기반입니다. selected/zoom-visible 후보만 health bar를 빌려 쓰고, hidden LOD fighter는 HUD slot과 pointer input을 해제합니다.

5.6 서버/API와 지표

  • 방문자 체크는 arena_visitor_id HttpOnly 쿠키와 MongoDB visitors 컬렉션을 사용합니다.
  • daily metrics는 앱 방문, 실제 전투 시작, 실제 전투 종료, 후원 클릭 예약 지표를 날짜별 합산 문서로 저장합니다.
  • death stats는 프리뷰가 아닌 실제 전투 종료 시 종족별 사망 수를 오늘 일자 문서에 누적합니다.
  • About 콘텐츠는 DB의 Markdown을 실시간 조회하며 서버 메모리 캐시를 두지 않습니다.

6. 기술 사양 및 튜닝 포인트

  • Framework: Phaser 3.90.0 (Arcade Physics 기반)
  • Build Tool: Vite 7.1.12
  • Server: Fastify 5.x (@fastify/static, @fastify/middie)
  • Database: MongoDB 7.x Node Driver
  • UI Logic: Vanilla JS & CSS
  • Render: RENDER.WIDTH/HEIGHT = 1280, ARENA.SIZE = 3200, CAMERA.MIN_ZOOM = RENDER_SIZE / ARENA_SIZE
  • Large Battle: PERFORMANCE.LARGE_BATTLE_* 상수에서 threshold, simulation buckets, aggregate refresh/cell/squad/death cap, target index refresh, HUD limit, Sprite budget, rolling window, dot redraw를 조정합니다.
  • World Effect: WORLD_EFFECT.*에서 첫/반복 포격, 밀집 경고 범위, 소형 strike 범위/개수/간격/시각 배율, meteor shake, fire/frost damage, frost stun/slow를 조정합니다.
  • Camera: CAMERA.LARGE_BATTLE_START_ZOOM, CAMERA.MANUAL_FOCUS_TWEEN_MS, CAMERA.MANUAL_FOCUS_TWEEN_EASE, meteor focus, spectator thresholds를 조정합니다.
  • Fighter: FIGHTER.DEAD_DESPAWN_DELAY_MS, FIGHTER.DEAD_DESPAWN_ALPHA, FIGHTER_TYPE_STATS, kill growth 상수를 조정합니다.
  • Worker fallback: LOD/aggregate worker는 성능 최적화 경로이며, 실패 시 main-thread 동기 path가 계속 동작해야 합니다.

7. 서버/API 설정

  • 개발 서버는 npm run dev, 운영 서버는 npm start, 정적 빌드는 npm run build로 실행합니다.
  • 기본 포트는 config.jsonSERVER_PORT 값이며 샘플은 9736입니다.
  • config.json은 로컬 설정 파일이므로 저장소에 커밋하지 않습니다. 새 환경에서는 config.json.sample을 복사해 사용합니다.

기본 API:

  • GET /api/health: 서버 및 MongoDB 설정 여부 확인.
  • POST /api/visitors/check: 방문자 UUID 쿠키 확인/발급 및 유니크 방문자 수 반환.
  • GET /api/visitors/stats: 전체 유니크 방문자 수 조회.
  • GET /api/daily-metrics/today: 오늘의 운영 지표 조회.
  • POST /api/daily-metrics/match-started: 실제 전투 시작 수 누적.
  • POST /api/daily-metrics/match-finished: 실제 전투 종료 수 누적.
  • POST /api/daily-metrics/donation-clicked: 후원 클릭 수 누적용 예약 API.
  • GET /api/death-stats/today: 오늘의 종족별 전투 사망 통계 조회.
  • POST /api/death-stats/today: 종료된 실제 전투의 종족별 사망 수 누적.
  • GET /api/about: 개발자정보와 개인정보처리방침 Markdown 조회.

8. 유지보수 규칙

  • 문서 동기화: 구조, 상수, API, 대규모 전투 path가 바뀌면 agent.md와 관련 context/*.md를 함께 갱신합니다.
  • 모듈 경계: ArenaScene은 orchestration을 맡고, fighter 상태/렌더 세부는 fighter/, 전투 판정은 combat/, match input/spawn은 match/, DOM UI는 ui/로 분리합니다.
  • Fighter 접근: 새 코드가 fighter body, animation, tint, velocity, position을 직접 다뤄야 한다면 먼저 fighterAdapter.js에 적절한 helper가 있는지 확인합니다.
  • Model-first 안전성: fighterForModelId()는 null을 반환할 수 있습니다. model-only 전투, stale id, death/unregister 이후 상태를 항상 고려합니다.
  • 대규모 전투 성능: 8,000명급 경로에서는 전체 fighter 배열을 매 프레임 스캔하거나 DOM/HUD/Graphics를 전원 갱신하지 않습니다. throttle, pool, worker, spatial index, set diff를 우선 사용합니다.
  • Phaser lifecycle: parked Sprite는 display/update list와 Arcade World에서 모두 빠져야 하며, reattach 시 model 좌표와 body를 동기화합니다.
  • 이펙트 lifecycle: pooled combat object는 releaseToPool/disposeCombatObject() 경로로 정리합니다. 새 이펙트도 match reset과 scene cleanup에서 누수되지 않아야 합니다.
  • API 변경: /api/* 경로는 Fastify route가 담당합니다. 개발 모드에서 Vite SPA fallback이 API 요청을 가로채지 않게 유지합니다.
  • 신규 캐릭터: public/assets/characters/에 에셋을 배치하고 fighterManifest.jsspecies와 combat/stat 정의를 추가합니다. 사망 통계 종족은 human, orc, skeleton, slime, wolf, bear 중 하나를 사용합니다.
  • 스타일 변경: src/styles.css는 모듈 import 엔트리입니다. 실제 수정은 src/styles/*.css의 해당 영역에서 진행합니다.

9. 관련 문서