Merge branch 'm9/replay' into codex/owner/grp_discord_joke-app

This commit is contained in:
EJClaw
2026-10-06 13:35:23 +09:00
19 changed files with 1612 additions and 12 deletions

View File

@@ -157,8 +157,9 @@
- 게임 상세 + 공개 방 목록
- 대기실(방장/참가자/관전자, 인원 부족/가득 참)
- 게임 화면(내 차례/남의 차례/연결 끊김/자리 비움/일시 정지)
- 결과 화면(승/패/무승부, 한 판 더 투표 중)
- 계정(게스트/디스코드)
- 결과 화면(승/패/무승부, 한 판 더 투표 중, 처음부터 다시 보기)
- 계정(게스트/디스코드, 내 게임 기록 목록 + [다시 보기])
- 다시 보기(`/replay/:gamePk`: 판은 읽기 전용, 처음/이전/재생·멈춤/다음/끝 + 슬라이더. PC는 오른쪽 칸, 폰은 아래 고정 막대. 못 보는 게임은 안내 문구, docs/09 §8)
- 오류(방 없음, 방 가득 참, 서버 업데이트, 오프라인)
## 11. PWA (홈 화면 추가·오프라인 안내)

View File

@@ -120,3 +120,12 @@ export interface GameUI<V> {
- 게임별 불변식(카드 총수 보존, 칩 총합 보존 등)
3. **정보 유출 검사**: 매 단계 각 플레이어 view의 JSON에 남의 비공개 카드 ID가 없는지(게임이 `secretsOf(state, player)`를 테스트용으로 제공).
4. **시간 초과 테스트**: 모든 상태에서 `onTimeout`이 합법 행동을 돌려주는지.
## 8. 게임 기록 다시 보기 (`apps/server/src/rooms/replay.ts`, `apps/web/src/pages/ReplayPage.tsx`)
- **다시 만들기**: 끝난 게임을 공개된 시드 + `room_log`의 `act`/`auto` 기록으로 처음부터 다시 돌린다. `setup`(시작 시각)과 각 `apply`(그 행동을 적용한 정확한 `now`, 로그의 `at`)를 방과 똑같이 부르고 RNG도 이어서 쓰므로 결과가 방에서 끝난 상태와 한 글자도 다르지 않다. `start` 로그에 `stateVersion`을 남긴다.
- **보이는 것**: 그 판에 앉았던 플레이어만 볼 수 있고(관전자·남은 403), 각자 게임 중에 받던 자기 `view`(있으면 `syncView`)만 받는다. 시드와 원본 로그는 서버 밖으로 나가지 않는다. 응답은 `private, no-store`.
- **장면**: 스트리밍 행동(그림 맞히기 선)이 아닌 행동 하나가 한 장면. 첫 장면만 view 전체, 나머지는 앞 장면과의 차이(`packages/shared/src/view-patch.ts`)만 보낸다. 다시 만든 결과가 저장된 결과와 다르면 보여 주지 않는다.
- **못 보는 경우**(410, 한국어 안내): 90일이 지나 로그가 지워짐, 그 뒤로 게임 규칙 버전(`stateVersion`)이 바뀜, 다시 만든 결과가 다름.
- **API**: `GET /api/me/games?before=&limit=`(내가 끝까지 한 게임, 최신순), `GET /api/replays/:gamePk`. 다시 만든 결과는 잠깐(10분, 32개) 캐시하고, 한 번에 하나씩 조금씩 끊어 만들어 방 처리를 막지 않는다.
- **화면**: 게임의 `Board`/`Side`를 그대로 쓰되 읽기 전용(`act`는 아무것도 안 함, 판 안의 누르기·키 입력은 막음). 처음 / 이전 / 재생·멈춤 / 다음 / 끝 버튼, 슬라이더, "n번째 / 전체 n번", 장면 설명(누가 움직였는지, 자동 진행). 키보드 ←/→/Home/End. 들어가는 곳: 결과 화면 [처음부터 다시 보기](`RoomView.lastGamePk`), 내 정보 › 내 게임 기록 [다시 보기].
- **테스트**: 모든 게임을 실제 `Room`으로 끝까지(무작위 행동, 시간 초과, 나가기) 둔 뒤 다시 만든 상태·결과가 같은지, 모든 장면이 그 플레이어가 받은 view와 같은지(`replay.test.ts`), 권한(`replay-api.test.ts`), 브라우저(`e2e/replay.e2e.ts`).