diff --git a/.atp/work-session/20260618-145152/report.md b/.atp/work-session/20260618-145152/report.md
new file mode 100644
index 0000000..b627ee8
--- /dev/null
+++ b/.atp/work-session/20260618-145152/report.md
@@ -0,0 +1,162 @@
+---
+schema_version: 2
+sid: 20260618-145152
+started_at: 2026-06-18T14:51:52+09:00
+ended_at: 2026-06-18T14:58:00+09:00 # 인터뷰 1차에서 사용자 지시로 일시정지 (구현 다음 세션)
+user_request: |
+ W3-2 댓글/리뷰 고도화 — 인터뷰 주도 디테일 빌드.
+ W3-2 코어(2026-06-18 구현·검증 완료) 유지하며 세세한 디테일을 사용자 인터뷰로 누적.
+ 바로 설계·구현 금지. 질문 시드 카탈로그(A 일관성 / B 목록규모 / C UX)를 한 축씩
+ AskUserQuestion 으로 물어 결정 누적 → design-advisor → 구현 → L1 회귀 31건 + 신규 → L3 스모크.
+mode: interview-first (orchestrator 직접 인터뷰, requirements-advisor 대체)
+---
+
+# Advisor Invocation Decision Log
+- advisor: requirements-advisor
+ decision: skip
+ rationale: '카탈로그가 요구 분해를 이미 제공 — orchestrator 가 AskUserQuestion 인터뷰로 직접 스코프 확정(task 명시)'
+ checked_at: 2026-06-18T14:52:00+09:00
+- advisor: design-advisor
+ decision: deferred-to-next-session
+ rationale: '인터뷰 완료·FINAL SPEC 사용자 확정. 단 사용자 지시로 구현은 다음 세션 → design-advisor 도 다음 세션 진입. 선결: W2-3 freeze 근거 확인'
+ checked_at: 2026-06-18T15:10:00+09:00
+- advisor: retrospective-advisor
+ decision: skip
+ rationale: '코드 변경 0줄·구현 미시작 인터뷰 전용 세션. 회고 입력(구현/검증 결과) 부재 → 구현 세션 종료 시 수행'
+ checked_at: 2026-06-18T15:10:00+09:00
+
+# Summary
+인터뷰 완료 — 카탈로그 전 축(A1·A2·A3 / B1·B2·B3 / 리뷰정책4 / C1~C6) 결정 누적 완료.
+사용자 지시로 이 세션은 **결정·기록만, 구현은 다음 세션**. 코드 변경 0줄(Serena/grep 탐색만).
+다음 세션: 새 sid + `resumed_from: 20260618-145152`, 아래 FINAL SPEC 으로 design-advisor 진입.
+
+## ⚠️ 다음 세션 선결·고위험 항목
+- **C3 = W2-3 평점 집계 동결 해제**: 설계 진입 전 W2-3 freeze 근거(ADR/문서) 필독. §6 파괴적 게이트 인접 — orchestrator 사용자 재확인. 회귀 검증 필수
+- **다축 평점 = 대규모 신규**: game_review_axes 테이블 + 6축 입력 UI + overall 자동평균/수동덮어쓰기 + game_review_stats 뷰(축별 평균). 작업량·테스트 비중 큼
+- 입력 부담: 리뷰 제출 = 6축 필수 + 본문 10자 — UX 마찰 인지
+
+# FINAL SPEC (✅ 사용자 최종확인 완료 2026-06-18 — 다음 세션 design-advisor 입력)
+
+## A. 일관성
+- **A1** 댓글 응답 = `commentView` 단일 통일(POST·PUT·list 전부). POST는 insert 후 재조회(createReview 패턴)
+- **A2** 작성자명 = 하이브리드 `COALESCE(u.display_name, c.nickname)` + LEFT JOIN. user_id 있으면 현재닉(개명반영), NULL이면 스냅샷(QG-2 해결). createComment nickname 스냅샷 저장 유지
+ - 탈퇴자: 댓글 보존(LEFT JOIN) / 리뷰 현행 제외 유지(INNER JOIN). 탈퇴자 댓글 이름 = "(탈퇴한 사용자)" 마스킹: `CASE WHEN u.id IS NULL THEN c.nickname WHEN u.is_delete THEN '(탈퇴한 사용자)' ELSE u.display_name END`
+- **A3** 댓글 edited+updatedAt 추가. game_comments.updated_at 멱등 ALTER + 매퍼/모델 갱신. editGameComment 시 updated_at 갱신, nickname 미덮어씀
+
+## B. 목록 규모
+- **B1** 페이지네이션 offset/limit + "더보기", 20건/페이지. 정렬 토글: 댓글(오래된순 기본/최신순), 리뷰(최신순 기본/별점 높은순/낮은순, tiebreaker rating·created_at·id). sort 화이트리스트 enum→고정 SQL(${} 금지)
+- **B2** 리뷰 본문 필수 + 최소 10자(별점만 제출 불가). 댓글은 현행(200자 상한, 최소 없음)
+- **B3** 본문 정규화 = 제어문자 제거 + 외곽 trim(댓글·리뷰 공통). 내부 연속공백·줄바꿈 보존
+
+## 리뷰 정책
+- 게임당 **1회 유지**(ux 유니크), 수정 **무제한 유지**(edited 마커)
+- **다축 평점**: overall rating 유지(W2-3 집계용) + game_review_axes 신규 테이블(review_id FK, axis_key enum, score). 축 6개 **몰입성·창의성·조작성·완성도·사운드·비주얼**, 각 1~5 정수 **전 축 필수**. UI: overall = 축 평균 자동계산(정수 반올림)되다 유저 직접선택 시 덮어쓰기(수동 우선, 출처 구분 플래그 검토)
+ - **다축 시각화 = 육각형 레이더 차트(hexagon)**:
+ - 위치 = **요약 + 개별 리뷰 둘 다** (요약=game_review_stats 6축 평균 육각형 / 각 리뷰 카드=그 리뷰 6축 육각형)
+ - 렌더 = **인라인 SVG 자체 구현** (polygon points 계산, 기존 JSP 인라인 SVG idiom·빌드 없음·의존성 0). 차트 라이브러리/CDN 배제
+ - 입력은 **6개 별점 위젯 유지(C6)** — 육각형은 표시 전용(레이더 드래그 입력 a11y 제외)
+ - **a11y**: SVG 시각요소엔 텍스트 대체 필수 — 축별 점수 aria-label 또는 인접 텍스트/표(스크린리더). 폴리곤만으로 끝내지 말 것
+ - 개별 리뷰 카드 육각형은 컴팩트 사이즈(페이지당 20건 DOM 고려)
+- 신고·숨김 **보류**(W1 운영자 role 선행)
+
+## C. UX (game-detail.jsp)
+- **C1** submit 잠금 + 로딩 상태(제출 중 disabled)
+- **C2** 상대시각 "n분 전" + title 절대시각(fmtDate 확장)
+- **C3** ⚠️ W2-3 동결 해제 + game_review_stats **뷰**(game_id, avg_rating, review_count + 6축 평균). 표시 = 소수1자리 + 반올림 별 + 0개 "아직 평가 없음". client 계산 폐기(페이지네이션 정합)
+- **C4** 실시간 글자수 카운터(123/200, 리뷰 최소10자 안내). 댓글·리뷰 둘 다
+- **C5** 삭제 = 완전 소멸 유지(현행)
+- **C6** 별점 위젯 완전 radiogroup 패턴(roving tabindex + 화살표/Home/End), 다축 6위젯 적용
+
+# Grounded Baseline (evidence)
+- GameCommentController 응답 3종 불일치:
+ - POST createComment flat: status,message,commentId,gameId,authorName,userId,content (createdAt/edited 없음)
+ - PUT updateComment 부분: status,message,commentId,content
+ - list commentView: commentId,gameId,authorName,userId,content,createdAt (edited 없음)
+- GameReviewController 이미 통일: POST/PUT/list/get 전부 reviewView 사용
+ - reviewView: reviewId,gameId,authorName,userId,rating,body,edited,createdAt,updatedAt
+- 댓글 작성자명 = nickname 스냅샷 (createComment: session displayName 저장)
+- GameCommentData 필드: id,gameId,userId,nickname,content,createdAt,deletedAt — **updatedAt 없음**
+ → A3(댓글 수정됨 마커)는 game_comments.updated_at ALTER 필요 (무료 아님)
+- createReview: rating 필수, body trimToEmpty 후 length 검사만 → 빈 본문 리뷰 허용 (B2)
+- trimToNull/trimToEmpty = 외곽 trim 만; 내부 연속공백/제어문자 미처리 (B3)
+- DDL game-reviews-ddl.sql: game_reviews(updated_at 보유), game_comments user_id nullable(레거시 보존)
+- W2-3 평점 집계 스키마 동결 / QG-2 레거시 user_id NULL 댓글 미결
+- **A2 작성자명 메커니즘 분기 (신규 발견)**:
+ - 리뷰 = `JOIN users u, u.display_name AS authorName` (GameReviewsMapper) → 항상 현재 닉네임(live)
+ + `u.is_delete IS NOT TRUE` 조건 → **탈퇴자 리뷰는 목록서 제외됨**
+ - 댓글 = `nickname` 컬럼 스냅샷(작성 시점 고정, GameCommentsMapper listGameComments:36) → 탈퇴자 댓글도 표시
+ - 댓글 매퍼에 미사용 `updateGameComment`(nickname 갱신 가능) 존재 (:81) — A2/A3 설계 시 활용 여지
+ - QG-2 결합: 레거시 user_id NULL 댓글은 JOIN 불가 → live-join 채택 시 스냅샷 fallback 필수
+
+# Decisions (누적)
+- A3 댓글 수정됨 마커: **edited+updatedAt 추가** (리뷰 대칭). game_comments.updated_at 멱등 ALTER + 매퍼 갱신 필요. [2026-06-18]
+- A1 응답 스키마: **commentView 단일 통일** — POST·PUT·list 전부 commentView 반환(commentId,gameId,authorName,userId,content,createdAt,edited,updatedAt). POST는 insert 후 재조회(createReview 패턴). 프론트 렌더 1함수. [2026-06-18]
+
+# Invocations
+[]
+
+# user_signals
+positive: []
+negative: []
+
+# verified_by_me
+(없음 — 코드 변경 0줄, 인터뷰만 수행. L1/L2/log-scan 비대상)
+
+# needs_user_verification
+- 다음 세션: 인터뷰 재개(아래 어젠다) → 결정 충분 시 design-advisor → 구현 → L1 회귀 31건+신규 → L3 스모크.
+
+# graph_refresh
+skip: 코드 변경 0줄 (graph-refresh-checker 비대상)
+
+# Next-session agenda (인터뷰 재개 지점)
+## 결정 잠금 (재확인만)
+- A1 = commentView 단일 통일 (POST·PUT·list 전부 commentView; POST insert 후 재조회)
+- A3 = 댓글 edited+updatedAt 추가 (game_comments.updated_at 멱등 ALTER + 매퍼/모델 갱신)
+ - 종속: A1 통일 시 commentView 가 edited/updatedAt 자동 포함 → 두 결정 한 묶음 구현
+- A2 = **하이브리드 작성자명**: `COALESCE(u.display_name, c.nickname)` + **LEFT JOIN users** (GameCommentsMapper getGameComment/listGameComments 둘 다)
+ - user_id 있으면 현재 닉네임(개명 반영, 리뷰와 일관) / NULL이면 스냅샷 nickname → **QG-2 자동 해결**
+ - createComment 의 nickname 스냅샷 저장은 **유지**(fallback 안전망). 미사용 updateGameComment(:81)는 edit 시 nickname 덮어쓰지 않도록 주의
+- A2-탈퇴자 = **댓글 보존 / 리뷰 현행(제외) 유지**: 댓글 LEFT JOIN(is_delete 무필터) → 탈퇴자 댓글 표시. 리뷰 INNER JOIN+`u.is_delete IS NOT TRUE` 그대로 → 리뷰 스코프·31건 테스트 무영향
+ - A2-탈퇴자-이름 = **"(탈퇴한 사용자)" 마스킹**. 댓글 SQL: `CASE WHEN u.id IS NULL THEN c.nickname WHEN u.is_delete THEN '(탈퇴한 사용자)' ELSE u.display_name END AS authorName` (LEFT JOIN). 탈퇴 플래그 노출 불필요 — 이름만 대체
+- B1 = **페이지네이션 도입 + 기본 정렬 현행 유지** (댓글 오래된순 ASC / 리뷰 최신순 DESC 기본). 전건 반환 폐기 → offset/limit 또는 cursor (방식·크기·정렬옵션은 후속 질문)
+ - B1-방식 = **offset/limit + "더보기" 버튼, 페이지당 20건**. page/offset 쿼리 파라미터
+ - B1-정렬 = **댓글·리뷰 모두 정렬 토글**:
+ - 댓글: 오래된순(기본)/최신순
+ - 리뷰: 최신순(기본)/별점 높은순/낮은순 — 별점순 정렬은 tiebreaker 필요(rating DESC/ASC, created_at DESC, id DESC)
+ - API sort 파라미터 신설 + 프론트 정렬 컨트롤. 매퍼 정렬절 동적화(단 `${}` 금지 — sort 화이트리스트 enum→고정 SQL 분기로 #{} 안전 유지)
+- B3 = **제어문자 제거 + 외곽 trim** (댓글 content·리뷰 body 공통). 탭/개행 외 제어문자 strip, 내부 연속공백·줄바꿈은 보존. textContent 렌더라 보안 아닌 저장 위생 목적
+- B2 = **리뷰 본문 필수 + 최소 10자** (별점만 제출 불가). createReview/updateReview body 검증 추가. 댓글은 별도(200자 상한만, 최소 없음 — 변경 안 함)
+ - 확장 리뷰 정책 4영역:
+ - ① 게임당 1회 제한 = **1회 유지(현행)** — ux_game_reviews_game_user_active 유지, 무결성 우선
+ - ② 수정 제한 = **무제한 수정 유지(현행)** — edited 마커로 투명성
+ - ③ 별점 척도 = **다축 평점 도입**. 세부:
+ - **overall rating 유지 + 축 별도** (W2-3 집계 동결 안 건드림 — 집계는 game_reviews.rating 그대로). 축은 game_review_axes 신규 테이블
+ - **UI 동작**: overall 별점 = 축 점수 평균으로 자동계산·표시되다가, 유저가 overall 을 직접 선택하면 그 값으로 강제 덮어쓰기 (수동 우선)
+ - 설계 미결: 자동평균→정수 rating 반올림 정책(C3 평균 별점 정책과 연동) / rating 수동·자동 출처 구분 필요(예 is_rating_manual 플래그?)
+ - **축 6개**: 몰입성, 창의성, 조작성, 완성도, 사운드, 비주얼
+ - 축 척도 = **1~5 정수, 전 축 필수** (overall 과 동일, 자동평균 결정적)
+ - 스키마 = **game_review_axes 테이블** (review_id FK + axis_key + score, 리뷰당 6행). axis_key enum 6종. DDL 신규 테이블 + FK + 인덱스(review_id)
+ - 다축 평점 = **풀스펙 확정**. 입력 부담(6축 필수 + 본문 10자)은 인지하고 진행
+ - ④ 신고·숨김 = **도입 보류(W1 선행)** — 운영자 role 활성 후 신고+숨김 함께. 현 세션 스코프 제외
+
+## 미결 — 다음 세션 질문 순서 (1~2축씩, AskUserQuestion, 추천 맨앞+근거+트레이드오프)
+3. **C UX (game-detail.jsp)** — JSP grounding 완료:
+ - C1 연타 방지 = **submit 잠금 + 로딩 상태** (제출 중 disabled, 응답 후 해제). 댓글 중복 POST 차단
+ - C2 시간 표시 = **상대시각 "n분 전" + title 속성에 절대시각** (fmtDate:1202 대체/확장)
+ - C3 평균 별점 = **⚠️ W2-3 동결 해제 + 집계 컬럼/뷰 신설** (사용자 informed 선택, 고위험 — 회귀 검증 필수). 표시 = **소수1자리 + 반올림 별 + 0개는 "아직 평가 없음" 문구**
+ - 모순 해소: client 계산은 B1 페이지네이션과 충돌 → 서버 집계로 전환 확정
+ - **다음 세션 설계 선결조건**: W2-3 freeze 근거(ADR/문서) 먼저 읽고 무엇이 왜 동결됐는지 파악 후 해제. §6 파괴적 게이트 인접 — orchestrator 사용자 확인 경로
+ - 리뷰별 overall 자동평균(축→overall)은 rating 정수라 정수 반올림 / 전체 평균 표시는 소수1자리 (반올림 컨텍스트 2종 구분)
+ - 집계 방식 = **뷰(VIEW) 기반** — game_review_stats 뷰(game_id, avg_rating, review_count + 6축 평균). 저장 컬럼 비신설(파생 객체만) → 동결 해제 중 회귀 표면 최소
+ - 축별 평균 = **overall + 6축 평균 모두 요약 표시**. UI에 축별 막대/점수. 뷰가 축별 AVG(game_review_axes JOIN) 포함
+ - C4 글자수 카운터 = **실시간 카운터** (123/200 형식, 리뷰는 최소 10자 미달 안내). 댓글·리뷰 textarea 둘 다 + (다축 도입 시 6축 위젯 입력 상태도 고려)
+ - C5 삭제 표시 = **완전 소멸 유지(현행)** — soft-delete + is_delete 필터. 댓글 스레드 없어 tombstone 불필요
+ - C6 접근성 = **완전 radiogroup 패턴** — roving tabindex(위젯당 단일 탭정지) + ←→↑↓·Home/End 별점 이동, aria 유지. 다축 6위젯 동일 적용. 현 위젯은 화살표 핸들러 없음(grep 확인) — 신규 추가
+ - grounding 근거: 별점위젯:1043(role=radiogroup/radio+aria 기구현), 평균:1034, 댓글ta:1080(max200), 리뷰ta:1052(max1000), 제출버튼:538(disabled 없음)
+
+## 인터뷰 종료 후 (task 명시)
+1. 누적 결정 최종 확인 → 2. design-advisor: 파일 소유권 맵 + 멱등 ALTER DDL → 3. 구현 → L1 31건 회귀+신규 → L3 스모크 → 4. docs/changes/ 이력 기록
+
+# open_items
+- `.atp/work-session/20260618-145152/report.md` (신규, untracked) — 세션 기록. 커밋은 사용자 지시 시 (CLAUDE.md 커밋 정책: 명시 요청 시만).
+- src/ 변경 0건 — 구현 미시작.
diff --git a/.atp/work-session/20260622-092800/artifacts/w3-2-ui-preview.html b/.atp/work-session/20260622-092800/artifacts/w3-2-ui-preview.html
new file mode 100644
index 0000000..286702e
--- /dev/null
+++ b/.atp/work-session/20260622-092800/artifacts/w3-2-ui-preview.html
@@ -0,0 +1,364 @@
+
+
+
+
+
+W3-2 UI 프리뷰 — 육각형 레이더 · 6축 평점 · 카드
+
+
+
+
+
+
W3-2 UI 프리뷰 · 육각형 레이더 · 6축 평점
+
+
+
+
+
+ 리뷰 요약 (game_review_stats)
+
+
+
+ 6축 평가 입력 (radiogroup · 키보드 ←→↑↓/Home/End)
+
+
+ 리뷰 목록 · 정렬 · 카드 컴팩트 레이더
+
+
+
+
+
+
+
+ 덧글 · 상대시각 · 글자수 카운터
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.atp/work-session/20260622-092800/documentation.md b/.atp/work-session/20260622-092800/documentation.md
new file mode 100644
index 0000000..3ffc613
--- /dev/null
+++ b/.atp/work-session/20260622-092800/documentation.md
@@ -0,0 +1,41 @@
+---
+phase: documentation
+agent: documentation-advisor
+agent_version: 1
+generated_at: 2026-06-22T10:00:00+09:00
+concerns:
+ - "C3 재분류(W2-3 동결 경계 재확인)는 되돌리기 어려운 아키텍처 경계 결정에 해당할 수 있으나, 실체는 기존 오분류를 roadmap 근거(:63/:201/:202)로 정정한 것이므로 현재는 changes/ 내 별도 섹션으로 기록. W2-3 관련 설계 분기가 재발하면 adr/ADR-0001 발행 권고."
+ - "design-advisor 가 이미 .atp/work-session/20260622-092800/implementation/design.md 에 설계 산출물을 남김. architecture/ 중복 갱신 없음 — changes/ + work-session/ 중심으로 처리."
+ - "frontend-design 스킬이 game-detail.jsp 시각 컴포넌트를 처리했으므로 JSP 프론트엔드 세부 구현은 changes/ 문서에서 요약 수준으로만 기록."
+ - "retrospective 교훈 4건은 development/verification-strategies.md 에 '설계·테스트 단계 체크리스트' 섹션 및 '프로토콜 개선 권고' 섹션으로 추가 완료 (2026-06-22 retrospective-advisor 수용 결정 반영)."
+ - "backport self-grep: verification-strategies.md 근거 블록(> 근거:)에 20260622·GameController.gameDetail 잔류 — 요구사항 '이번 세션 사례는 1줄 근거로' 허용 패턴이므로 정본 규칙형 텍스트에는 포함되지 않음. docs/development/release-checklist.md §7 부재 — 해당 절차 미적용(N/A에 준함)."
+concerns_checked: true
+---
+
+# 문서화 보고
+
+## 작성/수정된 문서
+
+| 경로 | 카테고리 | 유형 | 링크 추가한 index | 교차 링크 |
+|---|---|---|---|---|
+| `docs/changes/2026-06-22-w3-2-comments-reviews-enhancement.md` | changes | 신규 | `docs/changes/index.md` | `docs/changes/2026-06-18-w3-2-comments-reviews.md` (이월 항목 해소 링크) |
+| `docs/changes/index.md` | changes | 갱신 | — | — |
+| `docs/changes/2026-06-18-w3-2-comments-reviews.md` | changes | 갱신 (이월 항목 해소 표기) | — | `docs/changes/2026-06-22-w3-2-comments-reviews-enhancement.md` |
+| `docs/development/verification-strategies.md` | development | 갱신 (섹션 추가) | index 추가 불필요(기존 등록) | report.md#Retrospective memory_candidates docs_sync_target |
+
+## 의사결정 기록 위치
+
+- **C3 재분류**: `docs/changes/2026-06-22-w3-2-comments-reviews-enhancement.md` §C3 재분류 섹션 + `.atp/work-session/20260622-092800/implementation/design.md` §C3 재분류
+- **is_rating_manual 컬럼 채택**: `design.md` concerns 2번 + `report.md#Decisions`
+- **axis_key 저장형·순서 고정**: `design.md` concerns 5번 + §2.1
+- **sort 안전 (@SelectProvider 컴파일타임 상수)**: `design.md` §4 + `report.md#Decisions`
+- **pagination = limit+1 hasMore**: `design.md` §3.3 + `report.md#Decisions`
+- **SVG 육각형 = JS 런타임**: `design.md` §6.1 concerns 4번
+
+## 추후 문서화가 필요한 항목
+
+- **ADR-0001 (권고)**: W2-3 동결 경계 확정(잼 평가 한정, 댓글/리뷰 무관) — C3 재분류 정정의 배경이 된 경계 결정을 정식 ADR로 남길 가치 있음.
+- **DDL 적용 완료 시**: `docs/maintenance/` 에 dev/운영 DB 적용 절차(game_review_axes·game_review_stats·game_comments.updated_at·game_reviews.is_rating_manual) 기록 검토.
+- **L3 스모크 완료 후**: `docs/changes/2026-06-22-w3-2-comments-reviews-enhancement.md` 검증 결과 표에서 "needs_user_verification" → "PASS (날짜)" 갱신.
+- **기존 리뷰 axes 백필**: 백필 필요 판단 시 `docs/maintenance/` 에 절차 문서화.
+- **GET /reviews/mine (applyReviewGate 정밀화)**: 차기 세션 구현 완료 후 changes/ 업데이트.
diff --git a/.atp/work-session/20260622-092800/implementation/design.md b/.atp/work-session/20260622-092800/implementation/design.md
new file mode 100644
index 0000000..8f8df25
--- /dev/null
+++ b/.atp/work-session/20260622-092800/implementation/design.md
@@ -0,0 +1,536 @@
+---
+phase: design
+agent: design-advisor
+agent_version: 1
+generated_at: 2026-06-22T00:00:00+09:00
+concerns:
+ - "C3 재분류 확정: game_review_stats 는 W3-2 일반 DDL(읽기전용 집계뷰). W2-3 동결 묶음 아님 — 근거 jam-platform-roadmap.md:63,201,202. 단 game-reviews-ddl.sql:63 / db/schema.sql:136-137 의 '집계뷰 미신설' 보수 주석이 본 설계로 무효화되므로 구현 시 두 곳 주석 갱신 필수(미갱신 시 차기 세션 혼선)."
+ - "is_rating_manual 플래그 채택 — game_reviews 행 폭 증가(boolean 1컬럼). 채택 근거는 §5.3. 비채택 대안(매번 axes AVG 재계산)도 가능하나 '유저가 자동평균과 같은 값을 수동선택' 케이스를 구분 못 함 → 채택. 구현에서 이 컬럼이 실제 분기에 쓰이는지(수동/자동 표시 차별화 UI 없으면 dead) 재확인 필요."
+ - "GameReviewsMapper.editGameReview 의 axes 재저장은 delete-all + insert-6 채택(§5.2). upsert(ON CONFLICT) 대안 대비 단순하나, 6 DELETE+6 INSERT 트랜잭션. axes 매퍼 신규 분리 vs GameReviewsMapper 확장 — 신규 GameReviewAxesMapper 채택(§1, §5). 단일 책임."
+ - "SVG 육각형 좌표는 JS 런타임 계산 채택(§6). JSP 컴파일타임(JSTL) 대안은 개별 리뷰 N건 동적 렌더와 맞지 않음 — 기존 buildStarDisplay 패턴(JS DOM 생성)과 일관. 좌표 공식은 §6 에 고정."
+ - "axis_key DB 저장형 = 영문 snake(immersion/creativity/controls/completeness/sound/visual). 한글 라벨(몰입성 등)은 JSP 상수 매핑. enum 순서 = 육각형 각 축 인덱스(0~5)와 1:1 고정 — 순서 바뀌면 레이더 축 위치 변동. §2.1 / §6.3 에 순서 고정."
+ - "B2 최소 10자: 기존 createReview 는 빈 본문 허용(report Grounded Baseline:80). 본 변경으로 별점만 제출하던 기존 유저 플로우가 막힘 — UX 마찰. FINAL SPEC 확정 사항이므로 진행하되 프론트 안내문(C4 카운터 '최소 10자') 필수."
+ - "ESCALATION 없음 — requirements(report FINAL SPEC) vs research(roadmap) 충돌 0. C3 오분류는 task 가 이미 정정 지시 → 반영 완료."
+references:
+ requirements: /Users/wemadeplay/workspace/stz/bibimbap/.atp/work-session/20260618-145152/report.md
+ research: /Users/wemadeplay/workspace/stz/bibimbap/docs/work-log/2026-06-17-jam-platform-roadmap.md
+ adrs:
+ - /Users/wemadeplay/workspace/stz/bibimbap/docs/changes/2026-06-18-w3-2-comments-reviews.md
+ - /Users/wemadeplay/workspace/stz/bibimbap/docs/game-reviews-ddl.sql
+ - /Users/wemadeplay/workspace/stz/bibimbap/db/schema.sql
+---
+
+# 설계: W3-2 댓글/리뷰 고도화 (확정 구현 설계도)
+
+## 목표 / 비목표
+
+### 목표 (FINAL SPEC 추적 가능)
+- **A1** 댓글 응답 commentView 단일 통일 (POST·PUT·list 전부 동일 스키마, POST는 insert 후 재조회).
+- **A2** 댓글 작성자명 하이브리드 (LEFT JOIN users + CASE 마스킹). QG-2(레거시 user_id NULL) 자동 해결.
+- **A3** 댓글 edited+updatedAt (game_comments.updated_at 멱등 ALTER + 매퍼/모델 갱신).
+- **B1** 페이지네이션 offset/limit 20건 "더보기" + sort 토글 (화이트리스트 enum→고정 SQL, `${}` 금지).
+- **B2** 리뷰 본문 필수 최소 10자(trim 후). 댓글은 현행 유지.
+- **B3** 본문 정규화 (제어문자 strip + 외곽 trim, 댓글·리뷰 공통 유틸).
+- **다축 평점** game_review_axes 6축(1~5 전 축 필수) + overall 자동평균/수동덮어쓰기(is_rating_manual).
+- **C3** game_review_stats 읽기전용 집계뷰 (avg_rating, review_count, 6축 평균). 클라 평균계산 폐기.
+- **C1** submit 잠금+로딩 / **C2** 상대시각 / **C4** 글자수 카운터 / **C5** 완전소멸 유지 / **C6** radiogroup 완전 패턴.
+- **육각형 레이더** 인라인 SVG 자체구현 (요약 + 개별 리뷰 카드), a11y 텍스트 대체.
+
+### 비목표 (스코프 밖 — 명시)
+- 신고·숨김 (W1 운영자 role 선행).
+- 리뷰 이력 테이블 (in-row 마커만).
+- 좋아요 서버화 (별도 관리).
+- W2-3 잼 평가(심사/투표/시상) 스키마 — **본 설계 무관**(C3 정정으로 명확히 분리).
+- 댓글 최소 글자수 (현행: 상한 200자만).
+
+---
+
+## C3 재분류 (직전 report 오분류 교정 — 설계 결정)
+
+직전 report.md 는 C3(game_review_stats 평균별점 뷰)를 "W2-3 동결 해제·고위험·§6 게이트"로 표기했으나 **오분류**다.
+
+- **근거** (`docs/work-log/2026-06-17-jam-platform-roadmap.md`):
+ - `:63` — "W2-3 범위 = 잼 평가만. 댓글/리뷰 스키마 자체는 W3에서 설계(동결 묶음 아님)."
+ - `:201` — "S4D 평가 통합설계 → W2-3 범위 축소: 잼 평가(심사/투표/시상) 스키마만 동결. 댓글/리뷰 스키마는 W3-2에서 별도 설계."
+ - `:202` — "S4a 댓글/리뷰 분리 → W3-2 일반기능으로 재분류: 잼 평가 동결묶음에서 분리."
+- **결론**: `game_review_stats` = `game_reviews`(W3-2 테이블) 위 **읽기전용 집계뷰** 신설 = **일반 DDL**. §6 파괴적 게이트 없음. orchestrator 사용자 재확인 경로 불필요.
+- **부수 조치**: `docs/game-reviews-ddl.sql:63` 주석("집계 컬럼/뷰는 신설하지 않음 (W2-3 동결 보호)") 및 `db/schema.sql:136-137`("집계 컬럼/뷰는 W2-3 동결 — 신설 금지") 은 본 설계로 **무효화** → 구현 시 갱신 대상(파일 영향 맵 참조).
+
+---
+
+## 개요
+
+기존 W3-2 코어(댓글/리뷰 CRUD, 2026-06-18 커밋)를 보존하며 그 위에 일관성(A)·규모(B)·다축평점·UX(C)를 얹는다. 백엔드는 매퍼 시그니처 확장(offset/limit/sort, axes 저장) + 신규 axes 매퍼/DDL, 프론트는 game-detail.jsp 단일 파일의 댓글·리뷰 JS 블록 재작성(육각형 SVG, radiogroup, 페이지네이션, 서버집계 표시)이다. DDL은 game-reviews-ddl.sql 에 멱등 append + db/schema.sql 동기화한다. 전 변경은 31건 회귀 테스트를 깨지 않는 것을 1차 게이트로 한다.
+
+---
+
+## 플로우
+
+### 리뷰 작성 (POST /game/{id}/reviews) — 다축 확장
+```
+진입: CSRF(403) → 로그인(401) → 게임존재(404)
+ → rating 파싱(수동 overall, optional) + axes 6값 파싱(전 축 1~5 필수, 누락/범위외 400)
+ → body 정규화(B3) + 최소 10자 검사(B2, 400)
+ → 게임당 1회 검사(409)
+ → overall 결정:
+ 유저가 overall 직접선택 O → rating=선택값, is_rating_manual=true
+ 유저가 overall 직접선택 X → rating=round(6축 평균), is_rating_manual=false
+ → [TX] addGameReview(rating, body, is_rating_manual) → review.id 획득
+ → addReviewAxes(review.id, 6행) ← axes 매퍼
+ → getGameReview(review.id) 재조회 → reviewView(+axes) 반환
+종단: 200 reviewView
+```
+
+### 리뷰 수정 (PUT) — axes 재저장
+```
+진입: CSRF → 로그인 → 리뷰존재+게임일치(404) → 권한(canModify, 403)
+ → rating/axes/body 검증(작성과 동일)
+ → [TX] editGameReview(rating, body, is_rating_manual, updated_at=now())
+ → deleteReviewAxes(reviewId) ← 기존 6행 제거
+ → addReviewAxes(reviewId, 6행) ← 신규 6행
+ → getGameReview 재조회 → reviewView 반환
+```
+
+### 목록 조회 (GET /game/{id}/reviews?page&sort) — 페이지네이션
+```
+진입: 게임존재(404)
+ → page(default 0)·sort(default newest, 화이트리스트 검증→고정 enum) 파싱
+ → offset = page*20, limit=21 (hasMore 판정용 +1 조회)
+ → listGameReviews(gameId, offset, limit, sortEnum) ← 21건 조회
+ → 각 리뷰에 axes 6행 batch 조회·매핑 (listReviewAxesByReviewIds)
+ → hasMore = (조회건수 > 20) → 21번째 잘라 20건 반환
+ → game_review_stats 1행 조회(요약) → summary 포함
+종단: { status, reviews:[reviewView...], hasMore, summary:{avgRating,reviewCount,axes{}} }
+```
+
+### 댓글 작성 (POST /game/{id}/comments) — A1 통일
+```
+진입: CSRF → 로그인 → 게임존재
+ → content 정규화(B3) + 1~200자 검사
+ → nickname 스냅샷 = sessionDisplayName (유지)
+ → addGameComment → comment.id 획득
+ → getGameComment(comment.id) 재조회(LEFT JOIN authorName/edited/updatedAt) ← A1: 재조회 패턴
+ → commentView 반환
+종단: 200 commentView (createReview 패턴 대칭)
+```
+
+---
+
+## 데이터 모델
+
+### 2.1 game_review_axes (신규 테이블)
+| 컬럼 | 타입 | 제약 | 비고 |
+|---|---|---|---|
+| id | bigint | PK, seq | game_review_axes_id_seq |
+| review_id | bigint | NOT NULL, FK→game_reviews(id) | |
+| axis_key | varchar(20) | NOT NULL, CHECK(6종) | 영문 snake |
+| score | smallint | NOT NULL, CHECK(1~5) | |
+| | | UNIQUE(review_id, axis_key) | 리뷰당 축별 1행 |
+| | | INDEX(review_id) | 조회 |
+
+- **axis_key 6종 (순서 고정 — 육각형 축 인덱스 0~5)**:
+ `immersion`(몰입성), `creativity`(창의성), `controls`(조작성), `completeness`(완성도), `sound`(사운드), `visual`(비주얼).
+- 리뷰당 정확히 6행 (전 축 필수). DB는 UNIQUE+CHECK로 보장, 앱이 6행 누락 없이 insert 책임.
+
+### 2.2 game_comments.updated_at (멱등 ALTER)
+- `timestamptz DEFAULT now() NOT NULL`. `edited = updated_at > created_at` (리뷰 대칭).
+- 기존 행은 ALTER 시 default now() 적용 → created_at != updated_at 가능성 → 기존 행 보정: `UPDATE ... SET updated_at = created_at WHERE updated_at IS NULL` 불필요(NOT NULL default). 대신 ALTER 직후 `UPDATE game_comments SET updated_at = created_at WHERE updated_at > created_at` 1회로 기존 댓글이 "수정됨" 오표시되지 않게 정렬 (멱등 — 재실행 시 영향 없음, 신규 행은 INSERT가 created/updated 동시 now()).
+
+### 2.3 game_reviews.is_rating_manual (멱등 ALTER)
+- `boolean DEFAULT false NOT NULL`. true=유저 직접선택 overall, false=6축 자동평균.
+
+### 2.4 game_review_stats (VIEW 신규)
+- `game_reviews(is_delete IS NOT TRUE)` ⨝ `game_review_axes` 집계.
+- 컬럼: `game_id, avg_rating numeric, review_count bigint, avg_immersion numeric, avg_creativity numeric, avg_controls numeric, avg_completeness numeric, avg_sound numeric, avg_visual numeric`.
+- 클라 평균계산(updateSummary, JSP:1513-1522) 폐기 공급원.
+
+---
+
+## 외부 계약 (API)
+
+### 3.1 commentView 스키마 (A1 통일 — POST·PUT·list 동일)
+```json
+{
+ "commentId": 100,
+ "gameId": 1,
+ "authorName": "표시명 또는 (탈퇴한 사용자) 또는 스냅샷닉",
+ "userId": 7, // nullable (레거시)
+ "content": "...",
+ "createdAt": "ISO-8601",
+ "edited": false, // updated_at > created_at
+ "updatedAt": "ISO-8601" // A3 신규
+}
+```
+- 변화: POST 응답이 flat(commentId/gameId/authorName/userId/content) → commentView 전체. PUT 응답이 부분(commentId/content) → commentView 전체. createdAt/edited/updatedAt 신규 노출. `status`,`message`는 POST/PUT 응답에 commentView 와 병합(createReview 패턴 그대로).
+
+### 3.2 reviewView 스키마 (axes 추가)
+기존 reviewView(reviewId,gameId,authorName,userId,rating,body,edited,createdAt,updatedAt) + 신규:
+```json
+{
+ "...": "기존 필드 유지",
+ "ratingManual": false, // is_rating_manual
+ "axes": { "immersion":4,"creativity":5,"controls":3,"completeness":4,"sound":2,"visual":5 }
+}
+```
+
+### 3.3 GET list 응답 형태 (페이지네이션)
+- 댓글: `{ status, comments:[commentView...], hasMore }`
+- 리뷰: `{ status, reviews:[reviewView...], hasMore, summary }`
+ - `summary` = game_review_stats 1행 → `{ avgRating, reviewCount, axes:{immersion..visual} }` (review_count=0 이면 summary=null).
+- **요청 파라미터**: `?page=&sort=`.
+- **hasMore 방식 채택**: total count 대신 `limit+1` 조회 후 초과 여부. 근거 = 추가 COUNT 쿼리 회피, "더보기" UI는 total 불요.
+
+### 3.4 sort enum 값 정의
+- 댓글: `oldest`(기본), `newest`.
+- 리뷰: `newest`(기본), `rating_desc`, `rating_asc`.
+- 미지정/미허용 값 → 기본값으로 fallback (400 던지지 않음 — UX 관대).
+
+---
+
+## sort 화이트리스트 매핑 표 (§4 — `${}` 금지 안전성)
+
+매퍼는 **enum 분기 → 고정 SQL 절** 방식. 동적 `${}` SQL 치환 절대 사용 안 함. 컨트롤러가 String sort 를 화이트리스트 enum 으로 변환(미스매치=기본값) 후, 매퍼는 MyBatis ``(XML) 또는 **annotation @SelectProvider 의 if-분기로 고정 문자열 상수 선택**.
+
+**채택 방식**: 현 코드가 annotation 매퍼(@Select 텍스트블록)이므로 일관성 위해 **컨트롤러에서 sort enum 결정 + 매퍼 메서드를 sort별로 분리하지 않고**, `@SelectProvider` 로 전환하여 ORDER BY 절만 상수 분기. (대안: 매퍼 메서드 N개 분리 — 중복 과다로 비채택.)
+
+| sort enum | 대상 | ORDER BY 고정 SQL 절 (상수, 사용자 입력 미포함) |
+|---|---|---|
+| `oldest` (댓글) | comments | `ORDER BY created_at ASC, id ASC` |
+| `newest` (댓글) | comments | `ORDER BY created_at DESC, id DESC` |
+| `newest` (리뷰) | reviews | `ORDER BY r.created_at DESC, r.id DESC` |
+| `rating_desc` (리뷰) | reviews | `ORDER BY r.rating DESC, r.created_at DESC, r.id DESC` |
+| `rating_asc` (리뷰) | reviews | `ORDER BY r.rating ASC, r.created_at DESC, r.id DESC` |
+
+- **안전성 근거**: ORDER BY 문자열은 5개 **컴파일타임 상수** 중 하나로만 결정. 사용자 입력 String 은 enum 매칭(`switch`/`Map`)에만 사용되고 SQL 텍스트에 절대 보간되지 않음 → SQL injection 면역. offset/limit 은 `#{}` 파라미터 바인딩 유지.
+- **tiebreaker**: 별점순은 동점 시 created_at DESC, id DESC 로 결정적 정렬(페이지네이션 안정성 — offset 중복/누락 방지).
+
+---
+
+## 다축 시퀀스 (§5)
+
+### 5.1 overall 자동/수동 계산 위치 = **서버**
+- 클라(JS)는 6축 점수 + (선택적) overall 수동선택값만 전송.
+- **서버가 권위 계산**: overall 미전송/빈값 → `rating = Math.round(평균(6축))`, `is_rating_manual=false`. overall 전송 → `rating=전송값(1~5 검증)`, `is_rating_manual=true`.
+- 근거: 클라 계산은 변조 가능 + C3 서버집계 정합. round 정책 = 정수 반올림(HALF_UP, Java `Math.round` 는 .5 올림 — 일관).
+
+### 5.2 axes 재저장 전략 = **delete-all + insert-6** (updateReview)
+- `editGameReview` TX 내: `deleteReviewAxes(reviewId)` → `addReviewAxes(reviewId, List<6>)`.
+- 근거: upsert(ON CONFLICT) 대비 매퍼 단순. 6행 고정이라 성능 동일 수준. UNIQUE(review_id,axis_key) 와 무관(전삭제 후 삽입).
+
+### 5.3 트랜잭션 순서 (createReview)
+```
+@Transactional
+1. addGameReview(rating, body, is_rating_manual) → useGeneratedKeys → review.id
+2. addReviewAxes(review.id, [6 axis rows]) → 6행 batch insert
+ (실패 시 전체 롤백 — review+axes 원자성)
+3. getGameReview(review.id) + listReviewAxes(review.id) → reviewView 조립
+```
+
+### 5.4 axes 매퍼 = **GameReviewAxesMapper 신규** (단일 책임)
+신규 인터페이스. 시그니처(인자 사용목적 인라인 주석 — inflate 방지):
+```java
+@Mapper
+public interface GameReviewAxesMapper {
+ // 1리뷰 6축 일괄 저장. List 1건당 review_id/axis_key/score 사용.
+ int addReviewAxes(@Param("reviewId") long reviewId, // FK 대상 리뷰
+ @Param("axes") List axes); // 6행(axis_key+score)
+
+ // 수정 시 기존 축 전삭제(이후 add 재삽입).
+ int deleteReviewAxes(@Param("reviewId") long reviewId); // 대상 리뷰
+
+ // 목록 화면 N리뷰 축 batch 조회(N+1 회피).
+ List listAxesByReviewIds(@Param("reviewIds") List reviewIds); // 페이지 리뷰 id들
+}
+```
+- `ReviewAxisRow` = `{ Long reviewId; String axisKey; Integer score; }` (신규 POJO 또는 GameReviewData 내 정적 중첩). axes batch insert SQL은 `` (XML) 또는 @InsertProvider. **`#{}` 바인딩 유지**.
+- 최소 인자 원칙 적용: gameId 등 불필요 컨텍스트 미수신. 구현에서 확장 필요 시 추가.
+
+---
+
+## SVG 육각형 좌표 공식 (§6)
+
+### 6.1 계산 위치 = **JS 런타임** (game-detail.jsp 인라인)
+- 근거: 개별 리뷰 N건 동적 렌더(buildReviewItem) + 요약 1건. 기존 buildStarDisplay 가 JS DOM 생성 idiom → 일관. JSTL 컴파일타임은 동적 N건과 부적합.
+
+### 6.2 viewBox / 기하 상수
+- 요약(큰) 육각형: `viewBox="0 0 200 200"`, 중심 `cx=100, cy=100`, 반지름 `R=80`.
+- 개별 리뷰 카드(컴팩트): `viewBox="0 0 120 120"`, `cx=60, cy=60`, `R=44`. (페이지당 20건 DOM 고려.)
+- 함수는 cx/cy/R 파라미터화하여 공용 1함수로.
+
+### 6.3 6 꼭지점(축 끝) 좌표 공식
+- 축 i(0~5, §2.1 axis_key 순서와 1:1): 각도 `θ_i = -90° + 60°*i` (12시 방향 시작, 시계방향).
+ ```
+ angle_rad = (-90 + 60*i) * Math.PI / 180
+ axisX_i = cx + R * Math.cos(angle_rad)
+ axisY_i = cy + R * Math.sin(angle_rad)
+ ```
+- 배경 그리드(외곽 육각형) = 위 6점 polygon. 단계 그리드 = R*0.25, R*0.5, R*0.75, R 4단계 동심 육각형(`scale = level/4` 곱).
+
+### 6.4 score 폴리곤 점 공식
+- 축 i 점수 `s_i`(1~5) → 반지름 비율 `ratio_i = s_i / 5`:
+ ```
+ ptX_i = cx + R * (s_i/5) * Math.cos(angle_rad_i)
+ ptY_i = cy + R * (s_i/5) * Math.sin(angle_rad_i)
+ ```
+- `` (fill 반투명 + stroke).
+
+### 6.5 렌더 함수 시그니처 (inflate 방지 — 인자 사용목적 명시)
+```js
+// 6축 점수 배열을 육각형 SVG 엘리먼트로. 요약/카드 공용.
+function buildHexRadar(scores /* [6] axis순 점수배열, score폴리곤 */,
+ cx /* 중심x, 좌표기준 */,
+ cy /* 중심y, 좌표기준 */,
+ R /* 반지름, 스케일 */) { ... return