81 lines
9.9 KiB
Markdown
81 lines
9.9 KiB
Markdown
# 관악산 100명 공동 해돋이 설계
|
|
|
|
상태: 초기 설계 기록. 후속 합의에 따라 Node.js/Socket.IO, 기존 양 캐릭터 및 우천 장비 공유, MongoDB 접속 기록으로 구현했다. 최신 동작과 검증 결과는 `docs/multiplayer-server.md`를 참고한다. 서버 시작 시 기존 기록 일괄 갱신은 수행하지 않는다.
|
|
|
|
## 목적과 합의
|
|
|
|
관악산 한 공간에서 최대 100명이 각자 이동하며 다른 참여자의 캐릭터를 본다. Socket.IO를 사용한다. 공동 모드는 서버 기준 실제 현재 시각으로 고정하고, 날짜·시간 변경은 혼자 보기에서만 제공한다. 하조대·기사문은 비활성 상태를 유지하되 장소 추가에 공통 통신 로직을 재사용한다.
|
|
|
|
초기 게스트 접속과 메모리 관리를 제안한 대화에 이어 작성한 설계다. 아래 구체적 기본값은 구현자 제안이며 사용자가 각각 선택한 사항은 아니다.
|
|
|
|
## Approaches
|
|
|
|
- 선택한 통신: Node.js 단일 프로세스 + Socket.IO. room과 연결 수명 관리 기능을 활용한다.
|
|
- 대안: 직접 WebSocket 사용. 전송 계층은 단순하지만 입퇴장·재연결 프로토콜을 더 직접 구현해야 하므로 선택하지 않았다.
|
|
- 저장 제안: 참여자 Map과 장소별 상태를 서버 메모리에 유지한다. 영구 프로필이 없는 초기 범위에는 PostgreSQL을 도입하지 않는다. Redis와 다중 서버는 확장 요구가 생길 때 검토한다.
|
|
|
|
## 현재 코드와 재사용
|
|
|
|
- `src/constants/locations.ts`: 세 장소 정의와 관악산만 활성인 설정이 이미 있다. 장소 ID와 활성 상태는 서버와 클라이언트의 단일 출처로 유지한다.
|
|
- `src/ui/timeController.ts`: LIVE가 로컬 Date를 읽는다. 시각 공급 함수와 공동 모드 잠금을 추가해 재사용한다.
|
|
- `src/scene/world.ts`: 카메라 이동, 지면 높이 보정, 렌더 무효화, 조감 카메라, 시작점 복귀를 관리한다. 네트워크에는 관측용 조감 카메라가 아닌 실제 보행자 상태만 노출한다.
|
|
- `src/controls/summitWalking.ts`: 기존 보행 가능 영역 검증을 재사용한다. 순수 계산에 필요한 의존성만 최소한으로 분리하고 서버에서 렌더러를 로드하지 않는다.
|
|
- 속도는 `src/constants/movement.ts`의 기존 4m/s, 빠른 이동 20m/s를 기준으로 한다.
|
|
|
|
## 구성과 저장 경계
|
|
|
|
`server/`에 서버 시작과 입퇴장·상태 처리를 둔다. `src/multiplayer/`에 연결과 원격 캐릭터 처리를 둔다. 공유 이벤트 타입은 브라우저 전용 의존성이 없는 모듈에 둔다. 통신 주기·정원·보간 등 기능 상수는 `constants/`에 둔다. 세부 파일 분할은 구현자가 기존 패턴에 맞춰 결정한다.
|
|
|
|
서버는 플레이어 ID, 닉네임, 기본 외형 색상, 장소 ID, x/z 위치, 방향, 마지막 갱신 시각을 메모리에 저장한다. ID와 색상은 서버가 발급하며 클라이언트가 다른 사람의 ID를 지정할 수 없다. 좌표는 기존 장면의 미터 좌표계를 사용한다. 지면 y는 각 브라우저가 같은 지형에서 계산한다.
|
|
|
|
닉네임은 기본 자동 이름을 제공하고 입장 전 짧은 일반 텍스트 입력을 허용한다. 길이와 자료형을 검증하며 HTML로 렌더링하지 않는다. 로그인, 계정, 영구 외형 저장, 방문 기록, 채팅·음성, 결제는 범위 밖이다.
|
|
|
|
## 입장과 연결 수명
|
|
|
|
기본은 공동 보기이며 명시적 혼자 보기 전환을 제공한다. 장면 로딩이 끝난 후 입장을 요청한다. 관악산 room의 정원은 참여자 100명이며 자기 자신을 포함한다. 초기에는 연결 한 개를 참여자 한 명으로 취급하므로 여러 탭은 각각 정원을 사용한다.
|
|
|
|
서버는 장소 활성 상태와 정원을 확인하고 같은 동기 처리 구간에서 자리를 배정한다. 중복 입장은 멱등적으로 처리한다. 정원 초과나 비활성 장소 요청은 명확한 오류 코드로 거절한다. 101번째 참여자는 자동으로 다른 방에 배정하지 않으며 혼자 보기를 선택할 수 있다.
|
|
|
|
입장 응답에는 자신의 상태, 현재 참여자 전체 목록, 서버 시각을 포함한다. 서버가 Socket.IO 연결 종료를 감지하면 참여자를 제거하고 다른 사람들에게 퇴장을 전달한다. heartbeat 감지 전까지는 정원을 유지할 수 있다. 복구 시 이전 연결을 그대로 복원하려 하지 않고 새 입장과 전체 상태를 받는다. 중복 캐릭터와 이전 연결의 늦은 이벤트는 세션 경계에서 정리한다.
|
|
|
|
서버 재시작이나 재입장 시 기본 시작 위치로 돌아간다. 서버 장애 중에는 연결 끊김을 표시하고 원격 캐릭터를 정리한다. 공동 연결을 성공한 것으로 표시하지 않는다. 혼자 보기 전환 시 소켓을 끊고 정원을 반환한다.
|
|
|
|
## 이동 및 표시
|
|
|
|
로컬 보행은 기존 즉각 반응을 유지한다. 이동 상태는 최대 초당 10회 전송하고 서버는 검증된 변경분을 초당 10회 모아서 같은 장소에 방송한다. 정지 시 불필요한 상태 전송을 줄인다. 초기 전체 상태 및 주기적 전체 동기화로 상태를 수렴시킨다. 서버 시퀀스 번호로 오래된 업데이트를 무시한다.
|
|
|
|
서버는 유한한 수, 좌표 범위, 이동 속도와 경과 시간, 보행 가능 경로, 전송 빈도와 패킷 크기를 검증한다. 긴 비활성 기간이 순간 이동 허용으로 이어지지 않도록 이동 허용 시간은 제한한다. 거절 시 승인된 위치로 보정한다. 시작점 복귀는 별도 서버 명령으로 처리한다. 완전한 서버 물리 시뮬레이션이나 경쟁 게임 수준의 부정행위 방지는 범위 밖이다.
|
|
|
|
기본 캐릭터는 외부 모델 없이 가벼운 머리·몸통 형태와 색상으로 구분한다. 1인칭 자신의 몸은 표시하지 않는다. 다른 캐릭터는 약 100~200ms 보간 지연으로 표시한다. 원격 이동 및 보간 중에는 기존 렌더 무효화 흐름을 갱신한다. 공유 geometry/material을 사용하며 종료 시 자원을 해제한다. 캐릭터끼리 충돌하지 않고 시작 위치 겹침을 허용한다.
|
|
|
|
## 시간·장면·날씨
|
|
|
|
서버의 UTC epoch 시각을 요청/응답으로 받고 왕복 시간의 절반을 추정해 기준을 보정한다. 수신 후에는 monotonic 경과 시간으로 진행하고 주기적으로 재동기화한다. 네트워크 지연 때문에 완전한 프레임 일치는 보장하지 않는다. 표시 시간대는 기존 Asia/Seoul을 유지한다.
|
|
|
|
공동 모드에서는 날짜·시간 입력과 CUSTOM 전환을 UI 및 제어 로직에서 잠근다. 혼자 보기에서는 기존 LIVE/CUSTOM 동작을 보존한다. 공동 보기 복귀 시 현재 서버 시각과 새 입장 상태를 적용한다.
|
|
|
|
공동 모드는 기존 사진 기반 보정 지형 하나로 고정한다. 원본 지형 비교와 개발 프로파일 모드는 혼자 보기로 한정해 서로 다른 지면과 시간이 같은 room에 섞이지 않도록 한다. 기존 조감 기능은 유지하되 보행자 위치를 바꾸지 않는다.
|
|
|
|
공동 날씨는 서버가 관악산의 기존 기상 조회·캐시 로직을 재사용해 같은 자료와 상태를 배포한다. 브라우저 UI 제어와 순수 조회/보간 로직만 필요한 만큼 분리한다. 실패 시 모두 같은 기본 하늘 상태와 안내를 받는다. 혼자 보기의 과거 날짜 기상 조회는 기존 동작을 유지한다.
|
|
|
|
## 장소 확장
|
|
|
|
room 이름은 장소 ID로 정한다. 추가 장소에는 활성화 여부, 좌표 원점, 시작점, 보행 검증 정책을 연결한다. 장소별 분기는 입장/이동 이벤트 전체를 복제하지 않고 장소 설정과 지형별 정책에 한정한다. 하조대·기사문 활성화 및 실제 멀티플레이 검증은 후속 작업이다.
|
|
|
|
## 실행과 배포 범위
|
|
|
|
개발에서는 Vite가 `/socket.io`를 Node 서버로 프록시한다. 운영 실행에서는 Node 서버가 빌드된 정적 파일과 Socket.IO를 함께 제공하는 단일 인스턴스 구성을 기본으로 한다. 정적 파일 경로는 배포 루트 안으로 제한하고 출처와 입력 제한을 적용한다. 인터넷 서비스에서는 TLS 지원 프록시 뒤에서 실행할 수 있게 환경변수로 포트와 허용 출처를 설정한다.
|
|
|
|
이번 범위는 실행 가능한 서버 코드, 운영 빌드 실행 명령과 설명, 로컬 검증이다. 호스팅 계정·도메인·서버 사양을 정하거나 외부에 실제 배포하는 것은 포함하지 않는다. Git 저장소가 아닌 현재 작업 폴더에서는 설계 파일만 저장하고 커밋하지 않는다.
|
|
|
|
## 성공 기준과 검증
|
|
|
|
1. 100개 Socket.IO 클라이언트를 관악산에 동시에 입장시키고 101번째 거절, 한 명 퇴장 후 새 입장을 검증한다. 중복 입장과 비활성 장소 요청도 확인한다.
|
|
2. 100명이 10Hz 이동하는 5분 로컬 부하 시나리오에서 참여자 상태 일관성, 프로세스 안정성, 이벤트 지연, CPU·메모리 추이를 기록한다. 지연 p95 200ms 이하를 초기 로컬 목표로 삼되 결과와 하드웨어를 함께 보고하고 운영 성능 보장으로 표현하지 않는다.
|
|
3. 잘못된 좌표·과도한 속도·빈도, 시작점 복귀, 연결 종료/재접속, 정원 해제를 집중 검사한다.
|
|
4. 실제 브라우저 두 개에서 상호 입퇴장과 이동을 보고, 공동 시간 잠금·서버 시각 동기화 및 혼자 보기 복귀를 확인한다.
|
|
5. 99개 원격 캐릭터를 표시하는 브라우저 시나리오에서 프레임 시간과 자원 사용을 측정한다. 서버 부하 테스트와 렌더링 검증을 구분해 보고한다.
|
|
6. 기존 테스트와 TypeScript/운영 빌드를 수행한다. 기존 지형·천문 계산 동작은 유지한다.
|
|
|
|
세부 UI 배치, 색상 팔레트, 검증 허용 오차와 보간 조정은 구현자가 결정하고 측정으로 보완한다. 검증하지 못한 운영환경·모바일 성능은 명시한다.
|