Files
ssessesse-ki-battle/README.md
T

105 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 드래곤볼
가입 없이 봇 또는 친구와 대전하는 5개 행동 손놀이 게임.
## 실행
Node.js 24 이상, MongoDB Standalone이 필요하다.
```powershell
npm ci
Copy-Item .env.example .env
npm run build
npm start
```
`.env`의 `MONGODB_URI`, `MONGODB_DATABASE`, `HOST`, `PORT`를 설정한다. 기본 접속은 `http://localhost:3000`이다. 외부 브라우저에서는 **HTTPS**가 필요하다. 관리자 인증과 Web Crypto를 포함한 기능을 보호하는 프록시 뒤에 배치한다.
Fastify HTTP API·페이지·Socket.IO `/signal`은 한 앱 포트와 MongoDB 연결 풀을 공유한다. 앱 다중 인스턴스의 시그널링 공유는 구현하지 않았다. 개발 중 클라이언트는 다시 빌드하고, 서버는 `npm run dev`로 감시 실행한다. 실행 중인 배포 폴더를 덮어쓰지 않고 새 빌드를 준비한 뒤 교체한다.
## 대전과 연결
기존 화면·캐릭터·연출·공통 판정을 재사용한다. 봇 대전은 브라우저에서 실행한다. 친구 대전은 reliable ordered WebRTC DataChannel로 턴을 교환한다.
| 행동 | 기 소모 | 효과 |
|---|---:|---|
| 으 | 0 | 생존 시 기 +1 |
| 파 | 1 | 으를 맞힘 |
| 막기 | 0 | 파를 막음 |
| 에네르기파 | 3 | 으·파·막기를 맞힘 |
| 순간이동 | 1 | 파·에네르기파 회피 |
준비 3초 → 선택 4초 → 공개 1.2초. 기 0으로 시작하며 명중 한 번으로 종료한다. 첫 유효 선택을 잠그고 마감 전에 상대 선택을 공개하지 않는다. 같은 공격은 상쇄되며 공격·회피 비용은 실패해도 소모한다. 봇은 턴 시작 시점의 양쪽 기만 보고 미리 선택한다.
화면에는 내 기만 표시하고 행동 버튼은 내 캐릭터 아래에 배치한다. 상대 기는 화면에서만 숨긴다. P2P 판정·복원을 위해 양쪽 기가 브라우저에 보관되므로 개발자도구에서의 조회나 공개 행동을 통한 기 추적을 차단하지 않는다.
친구 대전은 마감 후 선택 해시를 교환하고 양쪽 해시가 모이면 선택과 nonce를 공개한다. 양쪽 브라우저가 독립 계산한 결과 해시와 확인 메시지가 일치해야 공개·기록한다. 한쪽 미입력은 패배, 양쪽 미입력은 무승부다. 명시적 나가기를 상대가 확인하면 기권 처리한다.
Socket.IO는 방 대기와 SDP/ICE 협상에만 사용하고 DataChannel 준비 확인 후 닫는다. 정상 대전 중 서버는 게임 객체·행동·판정 타이머를 유지하지 않는다. 서버 재시작 중에도 이미 연결된 대전은 진행하며 HTTP 결과 저장은 복구 후 재시도한다.
연결이 끊기면 최대 10초간 새 연결 epoch로 복구한다. 확정되지 않은 선택을 버리고 마지막 상호 확인 상태에서 선택 시간을 다시 시작한다. 복구 불가·합의 불일치는 `aborted`로 종료하며 승·패·무·연승을 바꾸지 않는다. 처음 연결은 최대 15초다. 모바일 백그라운드 정지나 장시간 탭 중단까지 연속 플레이를 보장하지 않는다.
## STUN / TURN
`.env.example`의 `STUN_URLS`, `TURN_URLS`, `TURN_SHARED_SECRET`을 운영 환경에 맞게 설정한다. URL은 쉼표로 구분한다. 직접 연결이 불가능한 네트워크에서는 TURN이 필요하다. TURN 포트·인증·방화벽과 중계 트래픽 비용은 별도 운영 대상이다.
서버는 방 참가자에게 coturn 호환 단기 HMAC 자격 증명만 제공한다. 공유 비밀값은 서버 환경에만 두며 `VITE_*` 변수나 번들에 넣지 않는다. ICE 설정이 비어 있어도 일부 로컬 네트워크에서 동작할 수 있지만 외부 접속 가능성을 뜻하지 않는다. 실제 TURN 경로와 모바일 네트워크 전환 검증 범위는 [전환 검증 기록](docs/verification/webrtc-migration.md)을 따른다.
## 접속 세션별 기록과 임시 방
- `gameSessionId`: 탭 `sessionStorage`의 게임 기록 단위. 같은 탭 새로고침은 복원하고, 저장 정보 없는 새 탭은 새 세션이다. 브라우저의 탭 복제·복원으로 정보가 복제될 수 있으며 한 세션은 미정산 대전 1개만 허용한다.
- `playerId`: 기존 익명 방문자 단위. 방문자 토큰은 `localStorage`에 보관한다.
- 분석 `sessionId`: 30분 비활동 후 나누는 방문 세션. 게임 기록 세션과 별개다.
게임 세션은 생성·대전 등록·결과 저장 시점부터 90일 TTL을 적용한다. 저장소 사용이 막히면 메모리 세션을 사용하며 새로고침 복원을 제공하지 못한다.
봇·친구 승/패/무·현재/최고 연승은 `game_sessions` 단일 문서에 보관한다. 승리는 연승 +1, 패배는 0, 무승부는 유지한다. 등록한 `matchId`와 순서 `seq`가 일치하는 결과만 원자적으로 반영하고 처리 영수증도 함께 저장한다. 중복 재전송·뒤늦은 과거 결과는 카운트를 올리지 않는다. 기록 요청에는 upsert를 사용하지 않고 세션 생성에만 사용한다. 다중 문서 트랜잭션은 필요 없다.
제출 결과는 클라이언트 보고로 받아들인다. 공개 경쟁 랭킹 수준의 서버 승리 검증이나 부정행위 방지를 제공하지 않는다.
결과 전송 실패는 2초·5초·10초·이후 30초 간격 및 온라인 복귀 시 재시도한다. 같은 탭에 미전송 결과를 보관하며 저장 확인 전에는 새 대전을 시작하지 않는다.
방은 `rooms` 임시 문서다. 6자리 코드나 초대 링크로 참가하며 정원·참가자·만료를 API에서도 검사한다. 대기 10분, 참가 완료 24시간 TTL이며 재대전 때 갱신한다. 양쪽 동의 후 새 `matchId`를 부여한다. 방 만료가 이미 등록한 결과 저장을 막지 않는다.
`players`는 익명 방문자 식별용으로 유지한다. 전환 전 `matches` 컬렉션과 서버 판정 전적·상세 조회는 제거했다.
사용하는 컬렉션은 `players`, `game_sessions`, `rooms`, `analytics_events`, `analytics_sessions`, `analytics_presence_samples`, `analytics_metadata`다.
## 운영 분석
`ADMIN_USER`, `ADMIN_PASSWORD`를 모두 설정하면 `/admin/analytics`, `/api/admin/analytics/*`를 HTTP Basic 인증으로 연다. 미설정이면 404다. 자격 증명은 서버에만 저장하며 외부 운영은 HTTPS를 사용한다.
새 분석은 HTTP 배치 최대 20건/5초, 보이는 화면의 활동 보고는 최대 60초당 한 번이다. 숨김 전환의 추가 보고도 분당 한 번으로 제한한다. 턴별 행동 저장·DB touch는 제거했다. 최근 90초 활동으로 활성 방문자를 추정하며 대전 중 사람 수·정확한 현재 대전 수는 미수집으로 표시한다.
관리자 화면은 클라이언트 대전 요약·접속 세션별 전적과 방문·유입·화면·활동 시간·방문 재방문 분석을 표시한다. 대전 수는 클라이언트 시작 보고의 ID로 중복 제거하고, 친구 한 판의 결과 보고는 참가자별 최대 2건이다. 전환 전 서버 판정에 의존하던 행동·턴 상세·완료율·플레이 전환율·재대전율은 제거했다.
기존 DB 정리는 앱을 중지한 상태에서 아래 명령으로 실행한다. 기본 실행은 대상 건수만 조회하며, 적용할 때는 새 백업 파일이 필요하다. 사용자 식별·방문 이력·현재 세션 전적·임시 방은 보존하고, `matches`, 구형 서버 전용 이벤트·접속 표본, `players.firstMatchAt`만 제거한다. 서버 시작 시 자동 삭제하지 않는다.
```powershell
node --env-file-if-exists=.env --import tsx scripts/cleanup-legacy-data.ts --database dragon_ball
node --env-file-if-exists=.env --import tsx scripts/cleanup-legacy-data.ts --database dragon_ball --apply --backup .work/legacy-cleanup/removed-data.ejson
```
이벤트·방문 세션·활동 표본은 90일 보관한다. 원문 UA·URL·방 코드·인증 토큰·IP·정밀 위치·오류 본문은 수집하지 않는다. 유입 도메인과 허용 캠페인 분류만 보관한다. 분석 전송 대기는 클라이언트 최대 100건·30분·6회 시도, 서버 최대 100건·30분이다. 분석 장애는 게임 진행과 결과 집계를 막지 않는다.
## 검증
```powershell
$env:TEST_MONGODB_URI='mongodb://172.16.0.7:27017/?directConnection=true'
npm test
npm run typecheck
npm run build
```
테스트는 `dragon_ball_test_*` DB만 생성·정리한다. 환경변수가 없으면 DB 테스트가 skip되므로 통합 검증으로 간주하지 않는다.
`tests/preview-server.ts`는 포트 3001의 로컬 검증 전용 서버다. `UI_PREVIEW_CLIENT`로 별도 빌드 경로를 지정할 수 있다. `/__test/peer`는 소켓·RTC 상태를 노출하는 진단 화면, `/__test/mobile`은 390×844 프레임(`?width=320&height=568` 지원), `/__test/desktop`은 1280×720 프레임이다. `/__test/admin`의 테스트 인증 프록시는 운영 빌드에 포함되지 않는다.
`node --import tsx tests/webrtc-load.ts`는 3천 세션의 60초 초기 유입과 15분 HTTP/DB 부하를 측정한다. 임의 이름의 전용 테스트 DB와 별도 프로세스를 사용한다. 실제 WebRTC 1,500쌍 또는 TURN 부하 검증을 대신하지 않는다.
[전환 지시서](docs/superpowers/plans/2026-10-12-webrtc-migration.md) · [전환 검증](docs/verification/webrtc-migration.md) · [진행 기록](docs/implementation-progress.md)
## 후속 범위
퀵매치·공개 랭킹·MMR·관전·채팅은 포함하지 않는다. 다중 앱 인스턴스, 분산 브라우저 1,500쌍, 운영 TURN 비용·실제 모바일 네트워크 검증은 별도 환경에서 확인한다.