Skip to content

Commit 2e5ec5f

Browse files
authored
Merge pull request #103 from hamlsy/release/3
Release/3
2 parents fda6aca + 065238f commit 2e5ec5f

97 files changed

Lines changed: 16785 additions & 15921 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
# ===========================================
55
# API Configuration
66
# ===========================================
7-
VUE_APP_API_BASE_URL=http://localhost:8080
8-
VUE_APP_WS_URL=http://localhost:8080
7+
VUE_APP_API_BASE_URL=http://localhost:8080/api
8+
VUE_APP_WS_URL=http://localhost:8080/api
99

1010
# ===========================================
1111
# Kakao Services

.env.development

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
# ===========================================
55
# API Configuration
66
# ===========================================
7-
VUE_APP_API_BASE_URL=http://localhost:8080
8-
VUE_APP_WS_URL=http://localhost:8080
7+
VUE_APP_API_BASE_URL=http://localhost:8080/api
8+
VUE_APP_WS_URL=http://localhost:8080/api
99

1010
# ===========================================
1111
# Kakao Services

KoSpot-frontend-private

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
# 멀티 결과창 화면상태 동기화 기획안 (Frontend 중심)
2+
3+
## 0. 문서 목적
4+
5+
- 게임 종료 후 `FinalResults` 화면과 `RoomView` 사이에서 플레이어의 현재 화면 상태(`IN_GAME`, `RESULT`, `ROOM`)를 실시간으로 동기화한다.
6+
- 현재 코드 구조를 기준으로, 구현 난이도 대비 리스크가 낮은 경로를 우선 채택한다.
7+
- 백엔드 문서 2종의 충돌 지점을 정리하고, 현 코드베이스에 맞는 단일 실행안을 제시한다.
8+
9+
---
10+
11+
## 1. 현재 플로우 재정리 (코드 기준)
12+
13+
### 1-1. Room -> Game
14+
15+
1. `RoomView.vue`에서 `useRoom.initializeRoom()` 실행
16+
2. `roomWebSocketService.connectToRoom()``/topic/room/{roomId}/playerList` 포함 방 채널 구독
17+
3. 게임 시작 시 `navigateToSoloGame()``BaseGameView.vue` 진입
18+
4. 라우팅 직전 `prepareForGameNavigation()`으로 Room leave 이벤트는 스킵하고 room 채널만 해제
19+
20+
### 1-2. Game 진행 -> 종료
21+
22+
1. `BaseGameView.vue`에서 `useSoloGameFlow.initializeFromServerStart(roomId)` 실행
23+
2. `soloGameWebSocket.js``/topic/game/{roomId}/game/finished` 수신
24+
3. `onGameFinish` 콜백에서 `showGameResults = true``FinalResults` 표시
25+
26+
### 1-3. 종료 후 이동
27+
28+
1. `FinalResults`에서 "방으로 돌아가기" 클릭 시 `BaseGameView.restartGame()` 호출
29+
2. Room detail API 조회 후 `RoomView`로 라우팅
30+
3. 현재는 이 구간 어디에서도 "내 화면이 RESULT/ROOM인지"를 서버에 전송하지 않음
31+
32+
---
33+
34+
## 2. 핵심 문제점
35+
36+
1. **상태 이벤트 미전송**
37+
- 게임 종료 후/방 복귀 시점에 화면 상태를 발행하지 않으므로 상대 클라이언트가 상태를 알 수 없다.
38+
39+
2. **수신 타입 미지원**
40+
- `roomWebSocket.service.js``GAME_ROOM_NOTIFICATION_TYPES``SCREEN_STATE_UPDATED`가 없어, 백엔드에서 내려도 기본 분기에서 무시된다.
41+
42+
3. **플레이어 모델에 화면상태 필드 없음**
43+
- `useRoom.transformGameRoomPlayers()` 결과에 `screenState`, `screenStateSeq`, `screenStateUpdatedAt`가 없다.
44+
- 따라서 UI 배지/문구로 노출 불가.
45+
46+
4. **역전 방지 규칙 부재**
47+
- 델타 이벤트와 전체 동기화가 섞일 때, 클라이언트에서 `seq` 기준 드롭 로직이 없어 상태 역전 가능성이 있다.
48+
49+
5. **문서 스펙 충돌**
50+
- 문서 A: 기존 `/topic/room/{roomId}/playerList` 통합
51+
- 문서 B: 신규 `/topic/game/{roomId}/screen/state` + snapshot
52+
- 현 코드 구조는 Room 채널 중심이므로 통합안이 변경 범위/리스크 측면에서 우세.
53+
54+
---
55+
56+
## 3. 아키텍처 결정 (권고안)
57+
58+
## 3-1. 채널 전략
59+
60+
- **P0 채택**: `playerList` 통합 전략
61+
- 수신: `/topic/room/{roomId}/playerList`
62+
- 타입: `SCREEN_STATE_UPDATED`(delta) + `PLAYER_LIST_UPDATED`(full sync)
63+
- 이유:
64+
1. RoomView가 이미 해당 채널을 구독 중
65+
2. 기존 알림 파이프라인(`GameRoomNotification`) 재사용 가능
66+
3. 신규 채널/권한/구독 lifecycle 추가 없이 구현 가능
67+
68+
## 3-2. 발행 전략
69+
70+
- 송신 endpoint는 백엔드 문서 기준으로 고정:
71+
- `/app/room.{roomId}.screen.state`
72+
- body: `{ state, clientSeq, clientTimestamp }`
73+
74+
## 3-3. 상태 정합성 전략
75+
76+
- 클라이언트는 memberId 단위로 마지막 `screenStateSeq`를 유지
77+
- 적용 규칙:
78+
- `incomingSeq < localSeq` -> drop
79+
- `incomingSeq === localSeq` -> no-op
80+
- `incomingSeq > localSeq` -> apply
81+
- `PLAYER_LIST_UPDATED`에서도 동일 규칙으로 멤버별 적용
82+
83+
---
84+
85+
## 4. Frontend 설계 상세
86+
87+
## 4-1. 데이터 모델
88+
89+
- 공통 enum:
90+
- `IN_GAME | RESULT | ROOM | DISCONNECTED`
91+
- P0 운영 상태:
92+
- 송신: `IN_GAME`, `RESULT`, `ROOM`
93+
- `DISCONNECTED`는 서버 leave 정책과 충돌 소지가 있어 P1 이후 검토
94+
95+
플레이어 객체 확장(최소):
96+
97+
```ts
98+
interface RoomPlayer {
99+
id: string;
100+
memberId: string | number;
101+
nickname: string;
102+
// ...existing
103+
screenState?: "IN_GAME" | "RESULT" | "ROOM" | "DISCONNECTED";
104+
screenStateSeq?: number;
105+
screenStateUpdatedAt?: number;
106+
}
107+
```
108+
109+
## 4-2. 상태 동기화 유틸 (신규)
110+
111+
- 위치: `src/features/game/multiplayer/room/services/screenStateSync.service.js` (권장)
112+
- 책임:
113+
1. `clientSeq` 증가기 관리(세션 단위)
114+
2. `sendScreenState(roomId, state)`
115+
3. 수신 이벤트 merge 유틸(`applyDelta`, `applyFullSync`)
116+
4. 상태 문구 변환 유틸(`toLabel`) 제공
117+
118+
## 4-3. Room 수신 처리
119+
120+
- `webSocketChannels.js`
121+
- `GAME_ROOM_NOTIFICATION_TYPES.SCREEN_STATE_UPDATED` 추가
122+
- `roomWebSocket.service.js`
123+
- `_handleGameRoomNotification``SCREEN_STATE_UPDATED` 분기 추가
124+
- payload를 `useRoom.handleGameRoomNotification`으로 전달
125+
- `useRoom.js`
126+
- `transformGameRoomPlayers`에서 screen state 필드 매핑
127+
- `handleGameRoomNotification`에 delta 머지 로직 추가
128+
- `PLAYER_LIST_UPDATED`는 full sync + seq 가드 적용
129+
130+
## 4-4. Game 화면 송신 처리
131+
132+
- `BaseGameView.vue` 전송 타이밍:
133+
1. 게임 화면 정상 진입 직후 `IN_GAME` 1회 전송
134+
2. `onGameFinish`에서 `showGameResults=true` 직후 `RESULT` 전송
135+
3. `restartGame()`에서 라우팅 직전 `ROOM` 전송
136+
- 실패 정책:
137+
- 전송 실패해도 UX(라우팅/버튼)는 블로킹하지 않음
138+
- 대신 RoomView 진입 후 `ROOM` 재전송으로 멱등 복구
139+
140+
## 4-5. Room 화면 송신 처리
141+
142+
- `RoomView.vue` 또는 `useRoom.initializeRoom()` 완료 직후 `ROOM` 1회 전송
143+
- 목적:
144+
- 게임 화면에서 ROOM 전송이 누락돼도 Room 진입 시 최종 보정
145+
146+
## 4-6. UI 반영
147+
148+
### Result 화면
149+
150+
- `FinalResults.vue`에 "상대 화면 상태" 섹션 추가
151+
- 문구 매핑:
152+
- `RESULT`: 상대가 결과 화면에 있습니다
153+
- `ROOM`: 상대가 방으로 돌아왔습니다
154+
- `IN_GAME`: 상대가 게임 화면에 있습니다
155+
- `DISCONNECTED`: 상대 연결이 일시 끊겼습니다
156+
157+
### Room 화면
158+
159+
- `SoloWaitingList.vue`, `TeamWaitingList.vue`(또는 `shared Player/Card.vue`)에 상태 배지 추가
160+
- 예시:
161+
- RESULT 배지: "결과 화면"
162+
- ROOM 배지: "방 대기"
163+
- IN_GAME 배지: "게임 중"
164+
165+
---
166+
167+
## 5. 문서 충돌 해소안
168+
169+
## 5-1. 충돌 정리
170+
171+
- 문서 A(백엔드 재검증본): playerList 통합 + delta/full
172+
- 문서 B(프론트 명세): 전용 채널 + snapshot
173+
174+
## 5-2. 최종 채택
175+
176+
- **P0는 문서 A 채택** (현 레포 구조와 일치)
177+
- 문서 B의 snapshot 개념은 **P1 옵션**으로 유지
178+
- 조건: 백엔드가 snapshot endpoint를 실제 제공할 때만 활성화
179+
180+
## 5-3. 구현 시 주의
181+
182+
- FE 코드에서 채널 문자열 하드코딩 금지, constants 경유
183+
- 타입 미등록 시 silent drop이 발생하므로 notification type 동기화를 우선 처리
184+
185+
---
186+
187+
## 6. 단계별 실행 계획
188+
189+
## P0 (필수)
190+
191+
1. notification type/모델 확장
192+
2. Room 수신 파이프라인에 `SCREEN_STATE_UPDATED` 반영
193+
3. `BaseGameView`/`RoomView` 전송 타이밍 반영
194+
4. Result/Room UI 최소 노출 반영
195+
5. seq 역전 방지 로직 적용
196+
197+
완료 기준:
198+
199+
- 2인 방에서 A가 결과창에 남고 B가 방으로 이동할 때, B 화면에서 A 상태가 1초 내 `RESULT`로 보인다.
200+
- A가 방으로 이동하면 B 화면에서 A 상태가 1초 내 `ROOM`으로 변경된다.
201+
202+
## P1 (정합성 강화)
203+
204+
1. 재연결 후 보정 전략 추가(가능 시 snapshot 요청)
205+
2. stale drop 카운트 로깅
206+
3. join 이벤트 payload 재검증(필드 유실 여부)
207+
208+
완료 기준:
209+
210+
- 중복/지연 이벤트 주입 시 상태 역전이 재현되지 않는다.
211+
212+
## P2 (운영 고도화)
213+
214+
1. `DISCONNECTED` 정책을 leave 정책과 함께 재정의
215+
2. 대규모 room 이벤트 부하에서 상태 반영 지연 측정
216+
217+
---
218+
219+
## 7. 테스트 시나리오
220+
221+
## 7-1. 기능 테스트
222+
223+
1. 게임 종료 직후 `RESULT` 전송/수신 확인
224+
2. 방 복귀 클릭 직후 `ROOM` 전송/수신 확인
225+
3. Room 최초 진입 시 `ROOM` 재전송 멱등 확인
226+
227+
## 7-2. 정합성 테스트
228+
229+
1. 동일 memberId에 `seq` 역전 이벤트 주입(`12 -> 11`) 시 drop
230+
2. 동일 `seq` 재수신 시 no-op
231+
3. delta 후 full sync, full sync 후 delta 순서 뒤바뀜 시 최종 상태 불변
232+
233+
## 7-3. 회귀 테스트
234+
235+
1. 기존 `PLAYER_LIST_UPDATED`/`PLAYER_JOINED`/`PLAYER_LEFT` 정상 동작
236+
2. 더미 모드에서 화면 상태 기능 미활성 또는 안전 무시
237+
3. 자동 퇴장(30초) 경로에서 leave/라우팅 회귀 없음
238+
239+
---
240+
241+
## 8. 리스크 및 대응
242+
243+
1. **백엔드-프론트 스펙 불일치**
244+
- 대응: 채널/타입/payload를 constants와 타입가드로 중앙화
245+
246+
2. **상태 역전으로 인한 UX 혼란**
247+
- 대응: memberId별 seq 가드 강제
248+
249+
3. **재연결 시 이벤트 유실**
250+
- 대응: P0는 full sync 복구, P1에서 snapshot 보강
251+
252+
4. **UI 과노출(배지 과다)**
253+
- 대응: Result/Room에서 상대 상태 중심으로 최소 노출
254+
255+
---
256+
257+
## 9. 작업 파일 제안
258+
259+
- `src/features/game/multiplayer/room/constants/webSocketChannels.js`
260+
- `src/features/game/multiplayer/room/services/roomWebSocket.service.js`
261+
- `src/features/game/multiplayer/room/composables/useRoom.js`
262+
- `src/features/game/multiplayer/roadview/views/BaseGameView.vue`
263+
- `src/features/game/multiplayer/roadview/components/results/FinalResults.vue`
264+
- `src/features/game/multiplayer/room/components/list/SoloWaitingList.vue`
265+
- `src/features/game/multiplayer/room/components/list/TeamWaitingList.vue`
266+
- `src/features/game/shared/components/Player/Card.vue` (팀모드 배지 공통화 시)
267+
- `src/features/game/multiplayer/room/services/screenStateSync.service.js` (신규)
268+
269+
---
270+
271+
## 10. 최종 결론
272+
273+
- 현 코드베이스에서 성공 확률이 가장 높은 경로는 **playerList 통합 + SCREEN_STATE_UPDATED delta 적용**이다.
274+
- P0의 본질은 "전송 타이밍 보장"과 "클라이언트 seq 가드"이며, 이 둘이 없으면 상태 표시는 재현성 있게 깨진다.
275+
- 따라서 이번 이슈는 UI 추가보다 먼저, **Room 수신 파이프라인 정합성 확보**를 우선 구현해야 한다.

0 commit comments

Comments
 (0)