Files
playuniare.sori.studio/docs/history.md

7.7 KiB

구조 및 의사결정 이력

2026-06-19: 독립 Vue 프로젝트로 시작

  • 기존 UA Deck Builder와 별도 Git 저장소로 분리했다.
  • 덱 빌더는 카드 선택과 덱 데이터 제공을 담당한다.
  • Play Uniare는 카드 조작과 실시간 세션을 담당한다.
  • 카드 이미지의 대규모 복제를 피하기 위해 기존 카드 자산 도메인을 참조한다.

2026-06-19: Vue 3, Vite, Tailwind CSS 채택

  • 사용자가 직접 유지보수할 수 있는 Vue 3를 선택했다.
  • 정적 빌드와 빠른 개발 서버를 위해 Vite를 사용한다.
  • 일관된 UI 작성을 위해 Tailwind CSS를 기본 스타일 방식으로 정했다.

2026-06-19: 카드 소유자만 조작하는 권한 모델

  • 플레이어는 자신이 소유한 카드만 조작한다.
  • 전투나 효과로 내 카드가 제거되어도 내 카드 소유자가 직접 정리한다.
  • 이 원칙으로 동시에 같은 카드를 수정하는 충돌을 줄인다.
  • 온라인 모드에서는 서버가 소유권을 최종 검증한다.

2026-06-19: Docker 기반 NAS 배포

  • UGREEN NAS에서 재현 가능한 배포를 위해 Docker Compose를 사용한다.
  • Vue 빌드에는 Node.js를 사용하고 실행 이미지는 Nginx를 사용한다.
  • 초기에는 테스트와 운영 컨테이너를 분리했으며 이후 운영 단일 구조로 단순화했다.

2026-06-19: 기존 카드 이미지 읽기 전용 공유

  • 기존 Uniare의 실제 웹 루트는 /volume1/docker/projects/apps/uniare/config/www로 확인했다.
  • 카드 원본은 해당 웹 루트의 images 폴더를 사용한다.
  • 새 프로젝트의 NAS 위치는 /volume1/docker/projects/apps/playuniare로 정했다.
  • 이미지 중복 저장을 막기 위해 Docker bind mount를 사용한다.
  • 원본 보호를 위해 컨테이너에는 읽기 전용으로 연결한다.
  • 브라우저에는 같은 출처의 /card-assets 경로로 제공한다.

2026-06-19: 실시간 서버 포트 정책

  • 흔히 사용하는 3000 대신 로컬 Socket.IO 기본 포트를 31873으로 정했다.
  • Docker 내부 포트는 다른 컨테이너와 중복될 수 있음을 명시했다.
  • NAS 호스트 공개 포트만 중복되지 않도록 관리한다.
  • 운영 환경에서는 포트를 직접 공개하지 않고 /socket.io 리버스 프록시를 사용한다.

2026-06-19: NAS HTTP 포트 환경변수화

  • 기존 테스트 포트 18080이 NAS의 다른 서비스와 충돌했다.
  • 테스트 기본 포트를 41808, 운영 기본 포트를 41809로 변경했다.
  • NAS별 포트 충돌에 대응할 수 있도록 .env와 Compose 변수로 관리한다.

2026-06-19: NAS 카드 이미지 ACL 대응

  • NAS 카드 파일은 POSIX 권한이 열려 있어도 ACL 때문에 기본 Nginx UID 101에서 읽기가 거부되었다.
  • 카드 파일 소유자 UID가 1000인 것을 확인했다.
  • Nginx 워커를 UID 1000의 cardreader 사용자로 실행하도록 변경했다.
  • Vue 정적 파일에는 빌드 단계에서 명시적인 읽기 권한을 적용했다.

2026-06-19: 로컬 테스트와 NAS 운영 단일화

  • 기능 및 Docker 검증은 개발 Mac에서 수행한다.
  • NAS에는 운영 컨테이너 하나만 유지한다.
  • 테스트 Compose 파일과 테스트 포트 환경변수를 제거했다.
  • 운영 주소를 http://playuniare.sori.studio로 확인했다.
  • 영구 데이터가 생길 때 적용할 마이그레이션 운영 원칙을 문서화했다.

2026-06-19: 대기실과 게임 화면 분리

  • Vue Router를 도입해 / 대기실과 /game 게임 화면을 분리했다.
  • Pinia를 도입해 방 코드와 플레이어 준비 상태를 관리한다.
  • 덱 등록 시 50장, 동일 카드, 트리거, AP 기본 규칙을 검사한다.
  • 게임 시작 준비로 첫 손패, 1회 멀리건, 라이프 7장을 구현했다.

2026-06-19: Socket.IO 온라인 대기실

  • Node.js Socket.IO 서버를 추가하고 방 상태를 서버 메모리에서 관리한다.
  • 6자리 방 생성, 방 코드 참가, 서버 덱 재검증, 준비 상태 동기화를 구현했다.
  • 양쪽 준비 완료 시 플레이어별 좌석과 자신의 덱 쿼리를 전달한다.
  • 브라우저는 같은 출처의 /socket.io로 연결하고 Nginx가 Docker 내부 playuniare-server:31873으로 프록시한다.
  • Socket.IO 포트는 NAS 호스트에 직접 공개하지 않는다.
  • 현재 서버 재시작 시 방이 사라지는 단순한 구조를 유지하고, 게임 상태 동기화와 재접속 복구는 후속 단계에서 구현한다.

2026-06-19: 온라인 선후공·턴·공개 카드 동기화

  • 선후공은 클라이언트 선택이 아니라 서버가 무작위로 한 명만 지정한다.
  • 양쪽 초기 손패 확정 후 선공부터 현재 차례를 시작한다.
  • 현재 차례인 플레이어만 서버에 턴 종료를 요청할 수 있다.
  • 플레이어는 자신의 공개 카드 상태만 제출하며 서버는 상대 상태를 직접 수정하는 요청을 제공하지 않는다.
  • 손패와 덱은 장수만 공유하고 카드 식별 정보는 서버 공개 상태에서 제외한다.
  • 구현 복잡도를 낮추기 위해 현재는 카드별 이벤트 대신 플레이어별 공개 상태 스냅샷을 전송한다. 재접속과 순서 보장은 후속 단계에서 보강한다.

2026-06-19: 손패 트레이와 단기 재접속

  • 상하 3행 플레이매트 구조를 유지하고 좌우 회전 배치는 채택하지 않았다.
  • 하단 Action Point 영역을 손패가 덮지 않도록 플레이매트 밖에 전용 손패 트레이를 추가했다.
  • 손패 카드는 축소해 한 줄로 표시하고 넘치는 카드는 가로 스크롤로 확인한다.
  • Socket.IO 소켓 ID와 별개인 플레이어 식별자와 복구 토큰을 도입했다.
  • 연결 종료 즉시 방에서 제거하지 않고 60초 동안 같은 좌석의 재접속을 허용한다.
  • 비공개 덱·손패와 카드 위치는 플레이어별 브라우저 저장소에 보관해 새로고침 후 복구한다.
  • 서버는 상대에게 공개할 카드만 보관하며 개인 손패 원본은 받지 않는 구조를 유지한다.

2026-06-19: 로컬 통합 개발 명령

  • Vue만 실행했을 때 발생하는 Socket.IO 연결 오류를 줄이기 위해 concurrently를 개발 의존성으로 추가했다.
  • npm run dev:all 명령 하나로 Vite와 Socket.IO 개발 서버를 함께 실행한다.

2026-06-19: 방 종료와 진행 중 게임 복귀

  • 방 상태가 꼬여도 사용자가 직접 초기화할 수 있도록 대기실과 게임 화면에 방 나가기를 추가했다.
  • 한 플레이어가 명시적으로 방을 나가면 방 전체를 종료하고 상대에게 종료 사유를 전달한다.
  • 대기실에서 기존 방을 종료하고 즉시 새 방을 만드는 동선을 추가했다.
  • 진행 중 게임 세션으로 재접속하면 서버가 덱 쿼리와 좌석을 다시 전달해 게임 화면으로 자동 복귀시킨다.
  • 명시적 종료와 서버의 방 종료 알림에서는 브라우저 재접속 정보와 개인 게임 스냅샷을 함께 삭제한다.

2026-06-22: 서버 권위 비공개 덱 순서

  • 서버는 카드 내용 대신 플레이어별 카드 인스턴스 식별자 순서만 보관한다.
  • 온라인 셔플은 Node.js 암호학적 난수를 사용하고 결과 순서를 해당 플레이어에게만 반환한다.
  • 드로우, 여러 장 공개와 라이프 배치는 서버 비공개 덱의 맨 위 식별자를 제거한 결과로 처리한다.
  • 덱 위·아래 이동은 서버 순서에도 반영해 화면 장수와 실제 드로우 결과가 어긋나지 않도록 했다.
  • 공개 카드 상태에는 단조 증가 리비전을 추가해 지연된 이전 상태가 최신 상태를 덮지 못하게 했다.