From d2e39a452ebb4091819577e93a6b30fd44a42015 Mon Sep 17 00:00:00 2001 From: Horoli Date: Tue, 8 Sep 2026 12:22:20 +0900 Subject: [PATCH] Initial commit: courtlab tactical board with team management and MP4 export --- .config.json.sample | 24 + .gitignore | 5 + AGENTS.md | 13 + DESIGN-NOTES.md | 36 + FASTIFY-SERVICE-PLAN.md | 69 + IMPLEMENTATION-NOTES.md | 48 + POV-DESIGN-REVIEW.md | 82 + SERVER-SETUP.md | 92 + TEAM-AND-VIDEO-SHARING-RESEARCH.md | 130 ++ VIDEO-SHARING-IMPLEMENTATION.md | 43 + index.html | 12 + package-lock.json | 3013 ++++++++++++++++++++++++++ package.json | 26 + server/app.js | 213 ++ server/app.test.js | 98 + server/bootstrap-admin.js | 22 + server/config.js | 96 + server/config.test.js | 69 + server/db.js | 37 + server/index.js | 15 + server/security.js | 48 + server/seed-qa.js | 19 + src/api.js | 42 + src/cameras.js | 50 + src/domain.js | 353 +++ src/domain.test.js | 412 ++++ src/editor.js | 44 + src/editor.test.js | 118 + src/main.js | 467 ++++ src/playOperations.js | 26 + src/playOperations.test.js | 43 + src/playRepository.js | 62 + src/playRepository.test.js | 64 + src/playback.js | 100 + src/playerAvatar.js | 40 + src/scene.js | 186 ++ src/state.js | 55 + src/style.css | 78 + src/ui.js | 116 + src/videoExport.js | 255 +++ src/videoExport.test.js | 102 + vite.config.js | 14 + 농구 전술보드 웹앱 MVP 작업지시서.md | 975 +++++++++ 43 files changed, 7812 insertions(+) create mode 100644 .config.json.sample create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 DESIGN-NOTES.md create mode 100644 FASTIFY-SERVICE-PLAN.md create mode 100644 IMPLEMENTATION-NOTES.md create mode 100644 POV-DESIGN-REVIEW.md create mode 100644 SERVER-SETUP.md create mode 100644 TEAM-AND-VIDEO-SHARING-RESEARCH.md create mode 100644 VIDEO-SHARING-IMPLEMENTATION.md create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 server/app.js create mode 100644 server/app.test.js create mode 100644 server/bootstrap-admin.js create mode 100644 server/config.js create mode 100644 server/config.test.js create mode 100644 server/db.js create mode 100644 server/index.js create mode 100644 server/security.js create mode 100644 server/seed-qa.js create mode 100644 src/api.js create mode 100644 src/cameras.js create mode 100644 src/domain.js create mode 100644 src/domain.test.js create mode 100644 src/editor.js create mode 100644 src/editor.test.js create mode 100644 src/main.js create mode 100644 src/playOperations.js create mode 100644 src/playOperations.test.js create mode 100644 src/playRepository.js create mode 100644 src/playRepository.test.js create mode 100644 src/playback.js create mode 100644 src/playerAvatar.js create mode 100644 src/scene.js create mode 100644 src/state.js create mode 100644 src/style.css create mode 100644 src/ui.js create mode 100644 src/videoExport.js create mode 100644 src/videoExport.test.js create mode 100644 vite.config.js create mode 100644 농구 전술보드 웹앱 MVP 작업지시서.md diff --git a/.config.json.sample b/.config.json.sample new file mode 100644 index 0000000..8669da7 --- /dev/null +++ b/.config.json.sample @@ -0,0 +1,24 @@ +{ + "mongodb": { + "uri": "mongodb://172.16.0.7:27017", + "db": "basket_utils" + }, + "server": { + "host": "0.0.0.0", + "port": 3000, + "mode": "development", + "allowedOrigins": [ + "http://localhost:5173", + "http://127.0.0.1:5173", + "http://192.168.5.10:5173" + ], + "secureCookie": false + }, + "bootstrap": { + "password": "" + }, + "qa": { + "db": "basket_utils_qa", + "password": "" + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89ce0aa --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +.env +.config.json +data/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..70f5bfb --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,13 @@ +# 프로젝트 작업 규칙 + +## 모델 역할 분담 + +- 실제 코드 작업(기능 구현, 버그 수정, 리팩터링, 테스트 코드 작성)은 **GPT-5.6 Luna (`gpt-5.6-luna`)**에게 위임한다. +- 주 에이전트는 요구사항 정리, 조사, 설계, 작업 지시, 코드 검토 및 결과 검증을 담당한다. 프로젝트 문서 수정은 주 에이전트가 수행할 수 있다. +- 코드 작업을 위임할 때 담당 파일과 완료 조건을 명시하고, 다른 작업자의 변경을 되돌리지 않도록 안내한다. +- Luna를 사용할 수 없으면 임의로 다른 모델로 구현하지 말고 사용자에게 상황을 알린다. + +## 변경과 검증 + +- 요청 범위 안에서 최소한으로 변경하며 기존 편집·재생 동작을 보존한다. +- 변경에 맞는 검증을 수행하고, UI 변경은 가능한 경우 PC와 모바일 화면에서 확인한다. diff --git a/DESIGN-NOTES.md b/DESIGN-NOTES.md new file mode 100644 index 0000000..af5b795 --- /dev/null +++ b/DESIGN-NOTES.md @@ -0,0 +1,36 @@ +# Court Lab — UI redesign + +2026-09-07 + +## Reference projects + +- [CoachCanvas](https://coachcanvas.app/): concise basketball product presentation, neutral surfaces, a clear primary action, and court-centered authoring. Its public website and welcome screen were inspected; no account was created and no assets were copied. +- [tldraw UI components](https://tldraw.dev/sdk-features/ui-components): separation of canvas, tool palette, property panel and navigation; properties move into a popover on mobile. This project uses the layout principles, not its SDK. +- `../network_dashboard/web/src/styles.css`: local reference for light surfaces, restrained borders, semantic color tokens and compact application controls. +- `../arena/src/styles/base.css` and `game-ui.css`: local reference for a dark central scene with unobtrusive tools. No files in either project were changed. + +## Design decisions + +- Warm white shell, dark evergreen canvas, terracotta primary actions, muted blue defense markers, procedural timber court. +- Court overlays use deep blue (`#174a7e`) movement lines and deep purple (`#5b2a83`) dashed pass arrows to contrast with the timber surface. Team marker colors remain independent of action colors. +- Header prioritizes identity, document name and save; setup, load, file operations and debug data live in the project menu. +- Left column shows lineup and possession. Right column shows action list and only exposes properties when an action is selected. +- Floating court tools use a consistent original SVG icon set and text labels. View controls stay at the top of the canvas. +- Playback occupies its own bottom deck. Step thumbnails derive from the actual sequence starting positions and action counts. +- Tactical view uses flat player markers and a diagram hoop. Physical hoop, backboard and articulated avatars remain available in Player POV. +- Phone portrait uses switchable panels. Landscape puts panel navigation on the left and action tools on the right. + +## Implementation + +- `src/ui.js`: markup, SVG icons and data-driven step thumbnails. +- `src/style.css`: design tokens, components and responsive layouts; replaces the earlier dark-dashboard stylesheet. +- `src/main.js`: existing editing handlers remain connected to the new layout; project menu dismissal and contextual properties. +- `src/scene.js`: procedural court material, team palette, flat markers and view-specific hoop representation. + +## Verification + +- Existing 91 tests pass; production build succeeds. Existing Three.js bundle-size warning remains. +- Browser inspection: 1440×1000 desktop, 390×844 portrait, 844×390 landscape. +- Verified action selection, gaze updates, playback, pause, menu opening and Escape dismissal; no browser errors observed. +- Landscape document dimensions match the viewport without horizontal or vertical overflow. +- Visual assets were authored in code. No reference screenshots, product logos or proprietary illustrations are embedded. diff --git a/FASTIFY-SERVICE-PLAN.md b/FASTIFY-SERVICE-PLAN.md new file mode 100644 index 0000000..931f2d7 --- /dev/null +++ b/FASTIFY-SERVICE-PLAN.md @@ -0,0 +1,69 @@ +# Fastify + MongoDB 서비스 전환 + +2026-09-08 + +## 확정 사항 + +- 사용자 지정 백엔드: Fastify. +- 사용자 최종 선택 데이터베이스: MongoDB `172.16.0.7:27017`. 이전 SQLite 선택을 대체한다. +- Fastify 인스턴스당 하나의 MongoClient 연결 풀을 유지하고 서버 종료 시 닫는다. 요청마다 연결·해제하지 않는다. +- 연결 URI와 DB 이름은 프로젝트 루트 `.config.json`으로 설정한다. `.config.json.sample`을 예시로 제공한다. 전용 DB `basket_utils` 사용은 사용자가 확인했으며 기존 다른 프로젝트 DB를 수정하지 않는다. +- 실제 구현: GPT-5.6 Luna, xhigh. 주 에이전트는 요구사항 정리·검토·검증·문서 담당. +- 범위: 로그인, 운영자 사용 승인, 여러 팀 생성/전환, 팀별 전술 저장/불러오기, 기존 기기 전술의 명시적 가져오기. +- 기존 Three.js 편집/재생과 MP4 파일 직접 공유는 유지. +- 외부 로그인 서비스 키가 없는 초기 구현은 이메일/비밀번호 로그인. 카카오톡 파일 공유와 서비스 로그인 수단은 독립적이다. + +## 사용자 흐름 + +1. 로그인 또는 회원가입. +2. 미승인 사용자는 승인 대기 화면. 승인된 사용자는 내 팀으로 진입. +3. 팀이 없으면 첫 팀 생성. 계정 하나로 여러 팀 생성 가능. +4. 팀 선택 후 해당 팀의 전술 목록과 새 전술 작성. +5. 편집 화면에서 현재 팀 이름을 확인하며 저장·불러오기·MP4 생성/공유. +6. 팀 전환 시 다른 팀의 목록/임시 저장과 섞이지 않음. +7. 로그아웃 시 편집/공유 상태를 정리하고 로그인 화면으로 복귀. + +## 검토 기준 + +- 회원가입이 자동 운영자 권한이나 자동 사용 승인을 부여하지 않는다. +- 운영자는 로컬 설정 절차로 초기화하며 공개 기본 비밀번호를 제공하지 않는다. +- 비밀번호는 해시로 저장하고 세션은 HttpOnly 쿠키로 관리한다. +- 서버가 매 요청의 승인 상태와 팀 소속을 확인한다. +- 팀 정보와 전술은 MongoDB에 저장되어 Fastify 재시작 후에도 남는다. +- 사용자·팀·전술명은 화면에서 데이터로 출력한다. +- 팀 변경/로그아웃 후 이전 요청 응답이 새 화면을 덮어쓰지 않는다. +- 저장 요청은 요청 시작 당시 팀에만 적용된다. +- 임시 저장은 사용자와 팀별로 분리한다. +- 기존 로컬 데이터는 사용자가 대상 팀을 고른 뒤 가져오고, 성공 전후 원본을 임의로 삭제하지 않는다. + +## 검증 시나리오 + +- 비로그인 → 로그인 필요; 미승인 로그인 → 대기; 승인 후 → 팀 화면. +- 팀 A/B 생성 → 각각 다른 전술 저장 → 왕복 전환/새로고침 후 분리 확인. +- 승인된 별도 계정은 멤버가 아닌 팀의 목록/전술에 접근 불가. +- 세션 로그아웃/승인 중단 후 서버 작업 거부. +- 기존 로컬 전술 가져오기 후 서버에서 다시 열기; 원본 로컬 데이터 보존. +- 편집/Undo/재생/선수 시점/MP4 공유 기능 회귀 확인. +- PC 및 모바일 화면 폭에서 로그인·대기·팀 선택·편집 상단 컨트롤 확인. +- 실제 테스트 계정은 격리된 QA 데이터베이스만 사용. 사용자 운영 DB에 임의 계정을 남기지 않는다. 기존 DB/컬렉션을 삭제하지 않는다. + +## 실행 구조 + +개발 중 Vite의 LAN 접속 주소를 유지하고 `/api` 요청을 Fastify에 프록시한다. 운영 모드에서는 Fastify가 빌드된 화면과 API를 제공한다. 구체적인 명령·환경변수·초기 운영자 절차는 구현 검토 후 실행 문서에 기록한다. + +휴대폰 HTTP LAN 접속과 OS 파일 공유 지원은 다르다. Web Share가 필요한 실제 공유 검증에는 지원 브라우저와 신뢰할 수 있는 HTTPS 환경이 필요하다. 이번 서버 전환이 카카오톡 실기기 전송 검증을 대신하지 않는다. + +## 검증 기록 + +- 2026-09-08: 사용자 지정 MongoDB에 드라이버 연결 및 `basket_utils` ping 성공. +- MongoDB 연결을 사용하는 Fastify 초기화, 프로젝트 인덱스 준비, `/api/health` 200 응답, `app.close()` 성공. 연결 풀 최대 크기 10 확인. +- 기존 102개와 Fastify 계약 테스트 4개를 포함해 106개 테스트 통과. 비로그인·승인 대기, 팀별 저장/조회, 다른 사용자 및 viewer의 저장 제한, 로그아웃·승인 중단 후 접근 제한, Secure 쿠키를 확인했다. +- `npm run dev`로 Fastify `0.0.0.0:3000`, Vite `0.0.0.0:5173` 실행 확인. localhost와 `192.168.5.10` 경유 API 정상 응답. +- 이 프로젝트의 예전 IPv6 전용 Vite 인스턴스가 localhost 요청을 가로채던 문제를 확인하고 해당 중복 프로세스만 종료. +- 브라우저 1440×1000 및 390×844에서 로그인·가입 화면 확인. 모바일 가로 넘침 없음. 휴대폰 실기기의 연결 확인과는 구별한다. +- 격리된 `basket_utils_qa_20260908review` DB에서 실제 화면으로 QA-A/QA-B 팀 생성·저장·왕복 전환·각 팀 임시 전술 복원 확인. 서버 재시작 후 세션 및 팀 데이터 유지 확인. +- 테스트 미승인 계정의 대기 화면, 운영자 승인 버튼, 승인 후 로그인, 새 계정에 기존 사용자의 팀이 나타나지 않음을 확인. +- 모바일 상단 버튼 줄바꿈 수정 후 현재 팀 이름과 공유·저장 아이콘이 한 줄로 표시됨을 확인. +- Fastify가 제공하는 빌드 화면에서 MP4 생성·재생 확인: 1280×720, 0.7252초, readyState 4, 끝까지 재생 완료. 팀 변경 후 이전 MP4가 제거되고 공유 버튼이 비활성화됨을 확인. 카카오톡 전송은 실행하지 않았다. +- 빌드 성공. 기존 500KB 초과 번들 경고는 남아 있다. +- 실행 및 최초 운영자 설정: [SERVER-SETUP.md](SERVER-SETUP.md). diff --git a/IMPLEMENTATION-NOTES.md b/IMPLEMENTATION-NOTES.md new file mode 100644 index 0000000..a2b013d --- /dev/null +++ b/IMPLEMENTATION-NOTES.md @@ -0,0 +1,48 @@ +# Court Lab 개선 기록 + +2026-09-07 + +## 적용 내용 + +- PC 코트 영역 확대, 개발용 JSON 접기, 한글 행동 이름. +- 모바일 세로 화면의 코트/선수/행동 패널, 가로 화면의 축소 도구와 전술 메뉴. +- 선택한 이동의 좌표 수정, 선택 선수 드래그로 시작 위치/이동 종점 수정. +- 시작 배치, 공 소유자, 단계 추가·삭제를 포함하는 전체 편집 Undo/Redo (최근 50개). +- 자동/공/림/이동 방향/선수/코트 지점 시선 선택. 모바일 대상 선택 취소 버튼. +- 패서의 자동 수신자 주시, 수신자의 비행 중 공 주시, 사용자 시선 우선. +- 재생 위치 탐색, 이전/다음 단계, 0.5~2배속, 전체 반복, 재생 완료 후 다시 시작. +- 기기 내 자동 임시 저장·새로고침 복원, JSON 입출력 (가져오기 2MB 제한). +- 슛을 유지한 기존 이동·시선 수정, 연결된 다음 단계 시작점 및 정지 패스·슛 위치 재계산. +- 림 3.05m, 공 소유/슈팅 높이, 시점 높이 1.75m. 세로 FOV 제한과 카메라 회전 속도 제한. +- 작전판은 마커, 선수 시점은 간단한 관절형 대체 모델. 선수 자신의 모델은 POV에서 숨김. + +## 검증 + +- `npm test`: 91개 테스트 통과. +- `npm run build`: 성공. Three.js를 포함한 단일 번들 크기 경고는 남아 있음. +- 인앱 브라우저: 390×844 모바일 화면의 이동 생성, 시선/좌표 수정, Undo, 패스, 반복 재생, POV, 저장 및 새로고침 복원 확인. +- 브라우저 개발 로그에서 오류 없음 확인. +- 1440×900 PC에서 코트 전체 표시와 선택 선수 드래그 확인. 844×390 가로 화면에서 코트/측면 도구/시간바 및 전술 메뉴 확인. 모바일 두 방향에서 문서 가로 넘침 없음. +- 실물 스마트폰 GPU/터치 성능은 별도 검증이 필요함. + +## 현재 제약과 후속 작업 + +- 단계당 패스 1회, 마지막 단계의 슛 1회 규칙은 유지. 슛이 있으면 새 행동·단계 추가는 슛 삭제 후 가능. +- 선수 및 시선 대상은 고정 5대5. 풀코트, 대기/드리블 독립 행동, 곡선, 단계 복제/정렬은 후속. +- 임시 저장은 현재 브라우저에만 저장. 공유 서버·계정·클라우드 동기화·PDF/영상 출력은 미구현. +- 3D 모형은 코드로 구성한 경량 대체 모델이며 Meshy/Tripo로 생성한 결과가 아님. 정밀 캐치·드리블·발 접지와 리타기팅은 미구현. +- 자유 시점 회전, POV 미니맵, 시선 전환 구간 세부 편집은 후속. + +## 외부 3D 제작 준비 + +첫 후보는 Meshy 공식 MCP로 원형 생성/리깅 후 Blender MCP로 보정. 외부 서비스 연결·API 키와 생성 크레딧이 필요하므로 이번 작업에서 설치나 유료 호출은 하지 않음. + +- Meshy: https://github.com/meshy-dev/meshy-mcp-server +- Blender MCP: https://github.com/ahujasid/blender-mcp +- Tripo 비교 후보: https://github.com/VAST-AI-Research/tripo-mcp + +납품 모델 권장 조건: GLB, 미터 단위, 바닥에 발 원점, 정면 축 확인, 하나의 일관된 휴머노이드 리그, 목/머리 분리 제어, 공은 별도 메시, 색상 변경 가능한 유니폼. 최초 검증은 한 명으로 진행하며 idle/run/defensive slide/screen/pass/shoot의 관절 변형과 공 이벤트 동기화를 확인한 뒤 10명으로 확장. + +시작 프롬프트 예시: + +> 성인 농구 선수, 단순하고 일관된 스포츠 게임 스타일. 민소매 유니폼과 반바지, 운동화. 공이나 소품 없음. 전신 중립 A-pose, 팔과 다리가 몸통에서 분리되어 보이며 리깅 가능한 구조. 읽을 수 있는 로고나 번호는 생성하지 않음. 정면·측면·후면 레퍼런스의 비율과 복장을 일치시킬 것. diff --git a/POV-DESIGN-REVIEW.md b/POV-DESIGN-REVIEW.md new file mode 100644 index 0000000..fa2359f --- /dev/null +++ b/POV-DESIGN-REVIEW.md @@ -0,0 +1,82 @@ +# 선수 시점 재생 정책 검토 + +2026-09-07 · 조사 및 권장 설계. 시점 주체 분리 등 아래 후속 정책은 아직 구현된 기능이 아니다. + +## 판단 + +기본은 사용자가 지정한 선수 한 명에게 시점을 고정한다. 동시에 움직이는 선수 수, 공 소유 변화, 단계 전환에 따라 자동으로 다른 선수에게 전환하지 않는다. 전술 전체는 작전판으로 보고, 선수 시점은 특정 역할에서 무엇을 보고 판단해야 하는지 확인하는 용도로 구분한다. + +| 선택 기준 | 장점 | 문제 | 권장 용도 | +| --- | --- | --- | --- | +| 움직이는 선수 자동 선택 | 별도 선택이 적음 | 동시 이동 우선순위가 자의적이고 정지한 스크리너·수비자의 역할을 놓침 | 기본 기능에서 제외 | +| 공 소유자 자동 선택 | 공 진행을 따라감 | 패스할 때마다 관찰 위치가 바뀌고 공중 구간의 주체가 애매함 | 추후 별도 관전 모드 | +| 사용자가 지정한 선수 고정 | 역할과 공간 관계가 일관됨 | 시야 밖 사건은 별도 보조가 필요 | 기본 선수 시점 | +| 단계별 지정 선수 | 코치가 의도한 설명 순서를 구성 | 설정 부담과 전환 규칙 필요 | 추후 설명용 재생 | + +## 외부 근거와 적용 범위 + +- [Epic 공식 리플레이 문서](https://dev.epicgames.com/documentation/fortnite/replays-feature-in-fortnite-creative?lang=en-US)는 선택한 선수를 따르는 Third Person, 해당 선수 카메라를 재생하는 Gameplay, 자유롭게 움직이는 Drone 계열을 분리한다. 시점 주체와 카메라 방식을 별도로 설계하는 참고 사례다. 농구 교육 효과를 입증하는 근거는 아니다. +- [VisionCoach 농구 패스 시각 훈련 연구](https://www.cs.ucf.edu/courses/cap6121/spr2025/readings/Liu2024.pdf)는 선수의 1인칭 관점에서 패스 기회를 찾는 훈련을 다룬다. 특정 역할의 시각적 판단을 돕는 용도에 부합한다. 이 연구는 본 웹앱의 자동 시점 전환 규칙이나 최적 화각을 검증하지 않았다. +- [Three.js PerspectiveCamera 문서](https://threejs.org/docs/pages/PerspectiveCamera.html)는 수직 FOV와 화면 종횡비로 원근 투영을 구성한다. 키와 화각은 서로 다른 변수이며, 세로 화면에서는 수직 화각 제한 때문에 가로 시야가 줄어들 수 있다. + +이 문서의 구체적인 UX 및 자동 시선 우선순위는 위 사례와 현재 코드 검토를 바탕으로 한 프로젝트 설계 제안이다. + +## 현재 코드의 의미와 문제 + +- `src/scene.js`는 `selectedPlayerId`의 위치와 시선 샘플로 POV를 계산한다. 현재도 움직이는 선수 자동 전환 방식은 아니다. +- `src/main.js`는 같은 선택값을 편집 대상에도 사용한다. 명단에서 선수를 바꾸면 재생 세션을 벗어나 편집 미리보기로 돌아가고, 패스 도구를 선택하면 공 소유자로 선택값을 바꾼다. 이 결합 때문에 시점 기준이 불명확하게 느껴질 수 있다. +- `src/domain.js`의 자동 시선은 패스·스크린 대상 선수, 슛의 림, 오프볼 공격자의 공, 대인 수비자의 매치업을 따른다. 이는 '누구의 눈인가'와 별개의 '무엇을 보는가' 규칙이다. +- 눈높이는 이미 1.75m이다. 키 180~190cm 선수를 가정한 초기 눈높이로 유지할 수 있으나 개인별 신체 계측값은 아니다. 키를 올리는 것만으로 좌우 시야가 넓어지지 않는다. + +## 권장 동작 + +1. 편집 대상 `selectedPlayerId`와 관찰 대상 `povPlayerId`를 분리한다. 처음 선수 시점에 진입할 때 선택한 선수를 관찰 대상으로 복사하고, 이후에는 명시적인 시점 선수 선택만 이를 변경한다. +2. 상단에 `O2 시점 · 선수 고정`을 항상 표시한다. PC에서는 선수 선택 드롭다운, 모바일에서는 같은 기능의 간결한 선택 패널을 제공한다. +3. 선택 선수의 행동이 없거나 이동이 끝나도 시점은 유지한다. 정지 상태에서 공과 다른 선수의 움직임을 관찰하는 것도 전술의 일부다. +4. 재생 중 다른 시점 선수를 명시적으로 선택하면 일시정지하고 현재 재생 시간을 유지한다. 새 위치로 즉시 전환하며 짧은 페이드와 선수명으로 전환을 알린다. 선수 사이를 카메라가 날아가는 연출은 피한다. 재생 버튼으로 이어 본다. +5. 단계 전환·반복 재생·패스 완료에도 관찰 대상을 유지한다. 관찰 선수가 데이터에서 사라진 경우 조용히 대체하지 말고 정지 후 재선택을 안내한다. +6. 작전판↔선수 시점 전환은 시간과 재생/정지 상태를 보존한다. 미니맵은 이후 보조 기능으로 제공하며 자신의 위치·시야 방향·공 위치를 표시한다. + +## 관찰 대상과 독립적인 시선 규칙 + +| 행동/상황 | 자동 시선 권장값 | +| --- | --- | +| 명시적으로 지정한 시선 | 해당 지시를 우선 적용 | +| 패스 준비·릴리스 | 패스 받을 선수 | +| 패스 수신 중 | 날아오는 공 | +| 슛 | 림 | +| 오프볼 이동 | 공을 기본으로 하되 이동 방향·특정 선수 지정 허용 | +| 공 소유 이동 | 림/전방을 기본으로 하되 전술별 명시 지정 허용 | +| 스크린 | 접근 중 이동 방향, 세팅 시 지정 수비자 등 단계별 구분을 후속 검토 | +| 대인 수비 | 매치업 기본, 공 주시는 명시 지정; 자동 양쪽 번갈아 보기는 초기 범위 제외 | + +눈길과 이동 방향을 동일하게 강제하지 않는다. 공을 보며 컷하거나 옆걸음으로 수비할 수 있어야 한다. 단, 현재 앱은 실제 눈동자·머리 움직임을 측정한 재현이 아니라 지정된 시선을 시뮬레이션한다. + +## 좁은 시야 개선 방향 + +- 코트의 물리 크기(15×14m)와 이동 거리를 늘리면 전술 자체가 달라진다. 카메라가 담는 범위와 화면 구성을 넓히는 것이 우선이다. +- 눈높이 1.75m를 기본으로 사용하고, 가로 화각을 완만하게 넓힌다. 이는 신체 키로 계산한 정답이 아니라 화면 가독성을 위한 초기 설계값이다. +- 선수의 시선 목표를 바꾸지 않으면서 화면에서 목표를 약간 위에 배치하여 바닥·주변 선수가 더 보이게 한다. +- 가까운 선수의 이름표 크기를 제한한다. 실제 가림은 유지하되 이름표 때문에 추가로 장면을 가리지 않도록 한다. +- 세로 화면에서 과도한 원근 왜곡 없이 가로 화면과 동일한 범위를 담는 데는 한계가 있다. 가로 보기와 후속 미니맵을 보조 수단으로 사용한다. + +## 후속 구현 검증 기준 + +- O1/O2/O3가 동시에 이동해도 O2 시점 유지. +- O1→O2 패스 동안 O3 시점 유지, O3는 기존 시선 규칙에 따라 공을 관찰. +- 정지한 스크리너·수비자를 선택해도 임의 전환 없음. +- 시점 선수 변경 시 같은 타임스탬프에서 일시정지; 다른 선수 위치로 전환한 뒤 이어 재생. +- 편집용 패스 도구·선수 선택이 관찰 대상을 덮어쓰지 않음. +- 단계 전환/탐색/반복/화면 회전 후 대상과 시간 일관성 확인. +- PC·모바일 세로·가로에서 대상 가시성, 가까운 이름표, 코트 바닥 범위를 확인. + +실제 코드 작업은 AGENTS.md에 따라 GPT-5.6 Luna에게 위임한다. + +## 이번에 적용한 시야 개선 + +- 기준 가로 화각 100° → 110°. 세로 화각 상한 85° → 100°로 완화. 화면 비율에 따라 실제 가로 범위는 제한될 수 있다. +- 눈높이 1.75m 유지. 코트 물리 크기 유지. +- 투영 영역을 높이의 8%만큼 아래로 옮겨, 시선 목표를 바꾸지 않고 바닥 영역을 더 표시. +- 선수 시점 이름표 축소 및 가까운 이름표의 화면 크기 제한. 이름표가 머리와 겹치지 않도록 위치 조정. +- 시점 주체 분리는 위 권장 설계로 기록했으며 이번 시야 개선에 포함하지 않았다. +- 검증: 테스트 93개 통과, 프로덕션 빌드 성공(기존 번들 크기 경고 유지). 1440×1000 PC, 390×844 세로, 844×390 가로 화면과 POV 재생 확인. 브라우저 오류 로그 없음. diff --git a/SERVER-SETUP.md b/SERVER-SETUP.md new file mode 100644 index 0000000..f1ce3d5 --- /dev/null +++ b/SERVER-SETUP.md @@ -0,0 +1,92 @@ +# Fastify · MongoDB 실행 안내 + +## 저장소 + +사용자가 지정한 MongoDB 서버는 `172.16.0.7:27017`, 프로젝트 DB는 `basket_utils`다. SQLite 선택은 이 설정으로 대체한다. 서버는 MongoClient 연결 풀을 재사용한다. + +프로젝트 루트 `.config.json`에서 설정을 관리한다. 예시는 `.config.json.sample`에 있으며 새 환경에서는 복사해 사용한다. 기존 설정 파일이 있으면 덮어쓰지 않는다. + +```powershell +Copy-Item .config.json.sample .config.json +``` + +```json +{ + "mongodb": { + "uri": "mongodb://172.16.0.7:27017", + "db": "basket_utils" + }, + "server": { + "host": "0.0.0.0", + "port": 3000, + "mode": "development", + "allowedOrigins": [ + "http://localhost:5173", + "http://127.0.0.1:5173", + "http://192.168.5.10:5173" + ], + "secureCookie": false + }, + "bootstrap": { "password": "" }, + "qa": { "db": "basket_utils_qa", "password": "" } +} +``` + +MongoDB 인증 정보가 필요하면 `mongodb.uri`에 설정한다. 실제 `.config.json`은 Git 추적에서 제외하고 예시 파일에는 비밀번호를 넣지 않는다. 설정 변경 후 서버를 재시작한다. + +## 개발 실행 + +```powershell +npm install +npm run dev +``` + +Vite 화면은 PC에서 `http://localhost:5173`, 같은 네트워크의 휴대폰에서 `http://192.168.5.10:5173`으로 접근한다. PC의 IP가 바뀌면 주소도 바뀐다. Vite의 `/api` 요청은 `server.port`에 지정한 Fastify 포트로 전달된다. + +LAN 접속에는 Windows 방화벽과 공유기의 기기 간 통신 허용도 필요하다. MongoDB 주소와 휴대폰이 접속할 웹 주소는 별개다. + +## 최초 운영자 + +공개 기본 운영자 계정은 만들지 않는다. 사용할 이메일로 회원가입한 뒤, 서버 PC에서 해당 계정을 명시적으로 승격할 수 있다. + +```powershell +node .\server\bootstrap-admin.js --email=your-email@example.com --promote-existing +``` + +`your-email@example.com`을 실제 가입한 이메일로 바꾼다. 기존 비밀번호는 유지된다. 이후 로그인하여 다른 사용자의 가입 신청을 승인한다. + +PowerShell에서는 `npm.ps1`을 통해 실행할 때 옵션이 npm 자체 설정으로 해석되어 스크립트에 전달되지 않을 수 있으므로 위처럼 Node로 직접 실행한다. 명령 끝에 역슬래시(`\`)를 붙이지 않는다. + +새 운영자를 직접 만들 때는 `.config.json`의 `bootstrap.password`에 사용할 비밀번호를 일시적으로 설정하고 아래 명령을 실행한다. 실행 후 비밀번호 값은 다시 빈 문자열로 바꾼다. + +```powershell +node .\server\bootstrap-admin.js --email=your-email@example.com +``` + +## 운영 실행 + +`.config.json`의 `server.mode`를 `production`, `server.allowedOrigins`를 실제 HTTPS 서비스 주소 배열, `server.secureCookie`를 `true`로 설정한다. + +```powershell +npm run build +npm run server +``` + +Fastify가 `dist` 화면과 API를 제공한다. 실제 도메인을 허용 출처로 설정하고 HTTPS 프록시 뒤에서 실행한다. 운영 세션 쿠키는 Secure를 사용한다. + +휴대폰의 HTTP LAN 화면 접속만으로 OS 파일 공유 조건이 충족되지는 않는다. 카카오톡으로 MP4 파일을 직접 공유하는 실기기 검증에는 지원 브라우저와 신뢰할 수 있는 HTTPS가 필요하다. + +## 검증 + +```powershell +npm test +npm run build +``` + +테스트 계정은 격리된 테스트 저장소에서 사용한다. 운영 `basket_utils` DB에 QA용 계정을 임의로 넣거나 기존 컬렉션을 삭제하지 않는다. + +2026-09-08 검증 결과: 106개 테스트 및 빌드 통과. 별도 QA DB에서 로그인·승인·다중 팀 저장/전환과 서버 재시작 후 데이터 유지, PC/모바일 화면, MP4 생성·재생을 확인했다. 기존 번들 크기 경고는 남아 있다. + +JSON 설정 전환 후에는 설정 로더 검증 5개와 정적 파일 보호 검증 1개를 추가해 총 112개 테스트와 빌드가 통과했다. 설정 파일이 없거나 JSON 구문·값 타입이 잘못되면 시작 시 오류를 안내하며, JSON 구문 오류에 설정 원문을 출력하지 않는다. 운영 모드에서는 Secure 쿠키가 강제되고 개발용 HTTP 출처는 허용하지 않는다. + +QA seed 명령은 `basket_utils_qa` 또는 `basket_utils_qa_` 뒤에 영문·숫자·하이픈이 붙은 DB 이름만 허용한다. 이번 검증 DB는 `basket_utils_qa_20260908review`이며 테스트 데이터는 운영 DB와 분리되어 있다. QA 서버는 검증 후 종료했다. diff --git a/TEAM-AND-VIDEO-SHARING-RESEARCH.md b/TEAM-AND-VIDEO-SHARING-RESEARCH.md new file mode 100644 index 0000000..6e4dd99 --- /dev/null +++ b/TEAM-AND-VIDEO-SHARING-RESEARCH.md @@ -0,0 +1,130 @@ +# 승인 사용자·다중 팀·전술 영상 공유 설계 조사 + +2026-09-07 · 조사/설계 제안. 이번 작업은 문서 작성이며 서비스 구현·배포·외부 API 등록은 수행하지 않았다. + +## 권장 결론 + +승인된 계정이 여러 팀을 소유하거나 참여하고, 각 팀에 전술을 저장한다. 공유 원본은 MP4로 생성한다. 사용자가 확정한 핵심 요구는 **방금 만든 전술을 MP4 첨부영상으로 카카오톡 단체방에 보내고, 수신자가 링크 이동 없이 카카오톡에서 재생하는 것**이다. 주 동작은 '영상 공유'이며 링크 공유는 요구를 충족하는 대안이나 fallback으로 취급하지 않는다. GIF는 후순위 비교 후보다. + +## 사용자 확정 요구에 따른 설계 변경 + +이 절이 아래 초기 비교/조사 내용보다 우선한다. 아래 링크 공유 설명은 조사 기록으로 남기며 제품 기본 경로로 채택하지 않는다. + +- 완료 흐름: 전술 작성 → 영상 공유 → 현재 버전 MP4 생성/준비 → 공유창에서 카카오톡·단체방 선택 → 영상 첨부 전송 → 수신자가 카카오톡에서 재생. +- 전송할 내용은 URL이나 JSON이 아닌 MP4 파일이다. 수신자의 웹사이트 로그인·팀 가입·외부 웹 플레이어 진입을 요구하지 않는다. +- 브라우저에서 Web Share 파일 전송을 먼저 검증한다. OS 공유창과 카카오톡 대화방 선택은 사용자 조작이다. 기존 단체방을 앱이 자동으로 지정하거나 무인 전송하는 요구로 해석하지 않는다. +- 파일 생성/준비에는 시간이 필요할 수 있다. 준비 완료 후 새 사용자 탭으로 OS 공유창을 열어 사용자 활성화 제한을 만족한다. 실제 측정 없이 최초 공유를 즉시 완료한다고 약속하지 않는다. +- 파일 공유 미지원 환경에서 다운로드 후 수동 첨부는 임시 보조 경로일 뿐, 핵심 UX 완료로 간주하지 않는다. 주요 대상 환경에서 직접 공유가 불안정하면 네이티브 앱 또는 하이브리드 앱의 파일 공유 브리지를 검토한다. 이때도 실제 카카오톡 수신 영상으로 검증해야 한다. +- MVP 통과 기준은 iOS/Android 실제 기기에서 생성 MP4가 기존 카카오톡 단체방에 첨부되고, 수신자가 외부 링크 없이 카카오톡에서 재생하는 것이다. 공유 API 호출 성공이나 파일 전송 가능 검사만으로 완료 처리하지 않는다. +- 검증 시 MP4가 일반 파일로만 표시되는지, 영상 썸네일과 재생 UI를 제공하는지 확인한다. 링크 없이 탭하여 재생하는 것이 요구이며 무조건 자동 재생을 의미하지 않는다. +- 발신자는 서비스 승인/팀 권한을 검사한다. 수신자는 카카오톡 영상 수신자이며 서비스 접근권한과 별개다. 전송한 파일은 앱에서 회수하거나 만료시킬 수 없다. + +카카오톡 메시지에 웹사이트 링크를 보낸 것과 MP4 첨부파일을 보낸 것은 다른 경험이다. 일반 Kakao Share 템플릿을 사용해 임의 MP4를 카카오톡 채팅방 안에서 자동 재생시키는 기능을 전제로 설계하면 안 된다. + +## 현재 프로젝트와 필요한 변화 + +현재는 Vite + Vanilla JS + Three.js의 로컬 편집 앱이며 `src/playRepository.js`에서 브라우저 localStorage에 전술을 저장한다. 사용자 인증, 사용 승인, 팀, 서버 저장, 공유 링크, 영상 렌더링 서버는 없다. + +새 UI에 팀 이름만 추가해서는 기기 간 동기화나 팀별 접근 제어가 성립하지 않는다. 인증/승인 API, 데이터베이스, 파일 저장소, 영상 생성 작업 처리가 필요하다. 현 전술 JSON 구조는 유지하고 서버 저장 레코드에 소유 팀과 버전 정보를 덧붙이는 방향이 적합하다. + +## 계정 및 팀 UX + +흐름: 로그인 → 서비스 사용 승인 확인 → 내 팀 → 팀 전술함 → 편집/재생 → 공유. + +- 로그인 수단은 카카오 로그인을 우선 후보로 제안한다. 카카오톡 공유를 하기 위해 서비스 로그인도 반드시 카카오여야 하는 것은 아니다. +- 로그인 성공과 서비스 사용 승인을 분리한다. 운영자 초대 또는 가입 후 승인으로 `pending/approved/suspended`를 관리한다. 미승인 사용자는 승인 대기 화면까지만 접근한다. +- 첫 진입에는 '첫 팀 만들기', 기존 사용자는 최근 사용 팀을 바로 열고 상단 팀 전환기를 제공한다. +- 계정 하나로 여러 팀을 만들고 전환할 수 있다. 팀명·선택적 색상/로고만으로 생성 가능하게 한다. 한 팀을 만든 뒤에도 항상 '+ 팀 만들기'를 제공한다. +- 팀 전술함은 전술 썸네일, 이름, 수정일, 태그, 영상 준비 여부를 표시한다. '새 전술'은 현재 팀에 자동 귀속된다. +- 편집 상단에 `팀명 / 전술명`을 표시해 다른 팀에 저장하는 실수를 줄인다. 다른 팀으로는 이동보다 '복사'를 기본으로 제공하며 목적지 편집 권한을 검사한다. +- PC는 좌측 팀 전환/전술함 + 넓은 편집 화면. 모바일은 상단 팀 전환기 + 전술 카드 목록 + 새 전술 버튼, 편집은 별도 화면으로 구성한다. +- 기존 기기 전술은 사용자가 선택한 팀으로 '이 기기의 전술 가져오기'를 제공한다. 서버 저장 성공 전 기존 데이터를 지우지 않으며 중복 가져오기를 방지한다. + +## 권한과 데이터 + +초기 역할 제안: 팀 소유자(팀/멤버 관리), 편집자(전술 작성·영상 생성·허용된 공유), 열람자(팀 전술 보기). 서비스 운영자의 승인 권한은 팀 소유자 역할과 별도다. 팀 초대는 서비스 이용 승인을 우회하지 않는다. + +| 레코드 | 핵심 관계/필드 | +| --- | --- | +| User | 로그인 제공자 식별자, 서비스 승인 상태 | +| Team | 팀 ID, 이름, 생성자 | +| TeamMember | 팀 ID + 사용자 ID, 역할, 가입 상태 | +| Play | 팀 ID, 전술 JSON, 현재 버전, 작성/수정자 | +| PlayRevision | 변경하지 않는 전술 스냅샷 | +| VideoExport | 전술 버전, 카메라/선수/화질 설정, 대기·처리·완료·실패 상태, 영상/썸네일 경로 | +| ShareLink | 공유 영상/버전, 열람 정책, 만료, 철회 상태, 추측하기 어려운 토큰 | + +모든 팀 데이터·영상 생성·공유 설정 API에서 서비스 승인 및 해당 팀 권한을 서버가 확인한다. 클라이언트의 teamId나 숨긴 버튼을 권한으로 신뢰하지 않는다. 기존 전술의 offense/defense는 작전판 진영이며, 새 Team 엔터티와 구분한다. + +## GIF / MP4 / 링크 비교 + +| 형식 | 적합한 점 | 제약 | 판단 | +| --- | --- | --- | --- | +| GIF | 짧은 반복 미리보기 | 3D 장면·텍스트의 화질/용량 부담, 재생 위치·속도 조절에 부적합, 수신 앱의 재생 방식에 종속 | 후순위 옵션 | +| MP4 파일 | 휴대폰에서 공유·저장하는 일반 영상, 작전판/POV 모두 가능 | 생성·전송 시간, 배포한 파일은 회수 불가 | 기본 영상 포맷 | +| 영상 재생 링크 | 작은 메시지, 열람 권한·만료·철회 가능, 웹 재생 컨트롤 제공 | 네트워크 필요, 채팅방 안 첨부영상과 다르게 링크를 열어 재생 | 기본 카카오 공유 방식 | + +MP4와 링크는 대안 관계가 아니다. 같은 MP4 자산을 링크 재생과 파일 공유 양쪽에서 사용한다. GIF 대비 영상의 용량 효율은 장면마다 다르므로 실제 작전판·POV 클립으로 비교한다. [Google web.dev](https://web.dev/articles/replace-gifs-with-videos?hl=en) + +## 카카오톡 링크 공유 + +권장 흐름: 공유 패널 → 썸네일/전술명/열람 범위 확인 → '카카오톡으로 링크 보내기' → 카카오톡에서 대화방 선택 → 수신자가 '전술 보기' 클릭 → 모바일 웹 플레이어. + +- Kakao JavaScript SDK의 `Kakao.Share.sendDefault()` 등으로 피드 카드를 보낸다. 썸네일·제목·재생 페이지 URL을 사용한다. +- Kakao Developers 앱, JavaScript 키와 사용 도메인 설정, 접근 가능한 HTTPS 재생 페이지가 필요하다. 비밀 키는 프런트엔드에 넣지 않는다. +- SDK의 공유 API와 친구 메시지 API는 다르다. 사용자가 기존 단체방을 골라 공유하는 요구에는 Share가 맞다. 우리 앱의 Team과 카카오톡 단체방은 자동 연결되지 않는다. +- 일반 템플릿은 링크 카드다. 썸네일에 재생 아이콘을 넣어도 임의 MP4의 채팅방 내 자동 재생을 보장하지 않는다. +- 재생 페이지는 `