Files
sunrise/docs/multiplayer-server.md
horoli eb49ea4b84 Improve summit terrain, mobile HUD and multiplayer night equipment
Add the Blender flashlight asset with a 600 lm beam and reduced bloom, shared-mode self badge, and compact phone controls. Relocate the summit marker, blend terrain seams, widen walking routes, and align local server configuration on port 2027.
2026-10-31 23:14:44 +09:00

7.4 KiB

관악산 공동 관측 서버

Node.js 22.18 이상. 단일 프로세스에서 gwanaksan 장소의 gwanaksan-001 방을 운영한다. 입장한 참여자는 최대 100명이며 자신과 다른 탭도 각각 포함한다. Socket.IO 클라이언트는 HTTP long-polling으로 연결 후 WebSocket으로 업그레이드할 수 있다.

실행

npm ci
Copy-Item .config.example.json .config.json
# .config.json에서 DB 주소와 허용 도메인을 편집
npm run build
npm start

예제 설정의 접속 주소: http://127.0.0.1:2027. DB: mongodb://172.16.0.7:27017, DB 이름 sunrise, 컬렉션 visitSessions. 인증이 필요하면 mongoUri에 계정과 authSource 등 해당 서버 설정을 지정한다. URI를 브라우저용 VITE_* 변수에 넣지 않는다. .config.json은 버전 관리에서 제외한다.

Linux에서는 cp .config.example.json .config.json으로 복사한다. 설정 파일은 작업 디렉터리와 관계없이 프로젝트 루트에서 읽으며 모든 예제 항목을 유지한다. 파일이 없거나 JSON 또는 항목 형식이 잘못되면 서버 시작을 중단한다. 서버 설정은 .env나 환경변수에서 읽지 않는다. 수정 후 서버를 재시작한다. npm run verify:mongo도 같은 파일의 mongoUri를 사용한다.

운영 환경에서는 allowedOrigins를 실제 접속 주소의 배열(예: ["https://sunrise.example.com"])로 바꾸고 visitStore는 "mongo"로 유지한다. JSON에는 주석을 넣지 않는다.

개발 시 별도 터미널에서 npm run dev:server와 npm run dev를 실행한다. Vite의 /socket.io, /health, /api 요청은 2027 포트 서버로 전달된다. npm run preview는 프런트 미리보기이며 공동 방 서버를 대신하지 않는다.

host 기본값은 127.0.0.1이라 현재 컴퓨터에서만 접속할 수 있다. LAN/운영 배포 시 실제 바인딩 주소, port, allowedOrigins를 지정하고 TLS를 제공하는 프록시와 방화벽을 구성한다. 이번 구현은 외부 호스팅 배포까지 수행하지 않는다. 동일 방/정원을 여러 Node 프로세스에서 따로 실행하지 않는다. 프로세스별 메모리는 공유되지 않으며 현재는 단일 인스턴스 구성이다.

동작

  • 공동 관측은 서버 UTC 시각을 기준으로 진행한다. 날짜 입력과 시간 슬라이더는 잠기며, 혼자 보기에서 기존 LIVE/CUSTOM 기능을 사용할 수 있다.
  • 위치·방향·인사/기도·우산·우비·후드를 서버 메모리에 저장한다. 초당 최대 10회 입력을 검증하고, 변경이 있으면 같은 방에 모아서 보낸다. 신규 입장에는 전체 참여자 상태를 전달한다. 5초마다 전체 상태도 다시 보낸다.
  • 현재는 100명 상한에서 단순성을 위해 변경이 있을 때 방 전체 스냅샷을 보낸다. 여러 방/대규모 확장 시 측정 결과에 따라 변경분 전송을 검토한다.
  • 보행 검증은 기존 관악산 경로·암반 영역과 이동 속도를 재사용한다. 위치 갱신 거리를 시간 기반 예산으로 제한하고 잘못된 좌표는 승인된 위치로 되돌린다. 완전한 서버 물리 시뮬레이션은 아니다.
  • 같은 장소의 날씨는 서버가 기존 조회·캐시 로직으로 읽어서 공유한다. 장비 착용 UI는 기존 비·눈 조건을 유지한다. 장비 자체는 영구 저장하지 않는다.
  • 같은 브라우저의 익명 visitorId를 localStorage에 보존하되, 로그인 인증이나 실제 사람의 신원으로 취급하지 않는다. 각 입장에는 별도의 서버 발급 방문 ID를 사용한다. 재접속 시 시작점과 장비 기본값으로 돌아온다. 시작점 복귀 버튼은 장비를 유지한다.
  • 연결이 끊기면 원격 캐릭터를 정리하고 재접속을 시도한다. heartbeat로 단절을 감지하기 전에는 자리가 잠시 유지될 수 있다. 혼자 보기로 전환하면 연결을 종료한다.
  • 101번째 참여자는 입장을 거절하고 재시도/혼자 보기를 제공한다. MongoDB 기록을 저장하는 동안에도 자리를 예약해 동시 입장으로 정원을 초과하지 않는다.

접속 기록

저장 필드: _id, visitorId, nickname, locationId, roomId, joinedAt, lastSeenAt, leftAt, endReason, serverInstanceId.

입장 기록 저장 성공 후 입장을 확정하고, 접속 중에는 60초마다 lastSeenAt을 갱신한다. 연결 종료와 정상 서버 종료에서 종료 정보를 기록한다. 위치·장비·감정표현은 DB에 쓰지 않는다.

서버 시작 시 과거 기록을 자동으로 변경하지 않는다. 강제 종료 또는 DB 장애로 퇴장 기록을 남기지 못하면 leftAt이 null로 남을 수 있다. 이는 현재 접속 중이라는 증거가 아니다. 실제 정원은 메모리에서 계산하며 lastSeenAt은 마지막 확인 시각일 뿐 정확한 퇴장 시각이 아니다. 로그 무손실 보장을 위한 디스크 큐와 자동 복구 작업은 포함하지 않는다.

DB 연결 실패 시 서버 시작을 중단한다. 실행 중 기록 저장 실패 시 /health는 503이고 신규 입장은 막으며 기존 참여자의 이동은 계속 처리한다. 원인을 해결한 후 서버를 재시작한다. 임의로 메모리 저장소로 전환하지 않는다. 개발·테스트에서만 visitStore=memory를 명시하면 기록을 영구 저장하지 않는 모드로 실행한다.

검증

npm test
npm run build
npm run verify:mongo
npm run test:load

verify:mongo는 매 실행마다 UUID가 붙은 임시 DB를 생성해 insert/heartbeat/finish/read 및 연결 시 기존 기록 보존을 확인한 뒤 그 DB만 삭제한다. sunrise 운영 데이터는 사용하지 않는다.

test:load는 메모리 저장소의 독립 로컬 서버와 100개 클라이언트를 한 프로세스에서 실행한다. 기본 300초이며 PowerShell에서 $env:LOAD_SECONDS='10'으로 짧은 실행도 가능하다. 보고서는 artifacts/multiplayer/load-report.json에 저장한다. 101번째 입장 거절·퇴장 후 자리 재사용·10Hz 이동을 검사한다.

2026-10-30 로컬 5분 결과: 오류 0, 이동 보정 0, 예상하지 않은 연결 종료 0. 방송 이후 수신 지연 p95 6ms, 최대 61ms. 이 값은 입력부터 방송까지의 대기 시간을 제외하며 실제 인터넷 지연·서버 단독 CPU 사용률·MongoDB 부하·모바일 GPU 성능을 보장하지 않는다. 두 브라우저에서 상호 캐릭터 표시/이동, 인원 표시, 혼자 보기와 재입장을 확인했다. 별도 메모리 검증 서버에서 99개 가상 참여자에 실제 브라우저 한 개가 입장해 100/100명 표시 및 양·우산·우비 렌더링을 확인했다. 해당 장면의 정량 프레임 시간과 모바일 성능은 측정하지 않았다.

주요 파일

  • server/app.ts: HTTP/Socket.IO 서버, 정적 파일, 상태 확인
  • server/room.ts: 정원 예약, 입퇴장, 상태 검증과 방송
  • server/visits.ts: MongoDB 방문 기록과 명시적 메모리 테스트 저장소
  • src/multiplayer/protocol.ts: 클라이언트/서버 공통 이벤트 타입
  • src/multiplayer/client.ts: 입장·재접속·현재 상태·시각 동기화
  • src/multiplayer/remotePlayers.ts: 다른 참여자의 양 캐릭터 표시와 보간
  • src/constants/multiplayer.ts, server/constants/multiplayer.ts: 통신·정원·서버 제한