최종 갱신 (KST)

← 일감 목록
T-038

해금 시스템 서버 구현 — 첫 적용은 작업슬롯

진우 Closed 우선순위 높음 목표 미정 · 보관됨

2026-09-16 — 완료. 서버 테스트 283건 통과 · Unity 에디터(unity CLI eval)로 실서버 왕복 검증 — 신규 2칸 · 선행/골드/재화/중복/없는 TID 거절 · 해금 후 칸 생성 · 치트 지급 · 재로그인 유지. 경위는 .claude/Agent/2026-09-16-unlock-server.md.

2026-09-14 — 슬롯 전용 해금(잠긴 칸 패킷·C_WorkStationUnlockRequest)에서 범용 해금 시스템의 첫 구현으로 바뀌었다 → 해금. 데이터(Unlock.xlsx — UnlockTable · WorkSlotTable)는 들어가 있다.

배경

슬롯을 여는 경로가 서버에 아예 없다. 기획은 시작 2개 · 상한 8개로 확정됐는데 서버는 User.DefaultSlotCount = 1이고, 패킷은 열린 슬롯만 담아 잠긴 칸을 표현하지 못한다. 클라는 1~7번 칸을 열린 빈 칸과 구분하지 못하고, 눌러 봐야 InvalidSlotIndex 거절뿐이다.

해금은 슬롯만의 문제가 아니라 산업 레벨·특성에도 같은 모양으로 필요해서, 한 표(UnlockTable)와 한 행동(C_UnlockRequest) 으로 모으기로 했다. 이 일감이 그 골격을 세우고 슬롯에 처음 적용한다.

규칙 (확정 · 상세는 기획 문서)

항목 값
조건 UnlockTable 행의 Gold · AccountLevel · RequiredUnlockTIDs. 0/빈 값 = 없음. 지불 컬럼끼리 OR, 나머지와 AND
판정 순서 없는 TID → 이미 열림 → 선행 미충족 → 레벨 미달 → 골드 차감(TrySpendGold 재사용) → 기록
영속 t_user_unlock (user_id, unlock_tid, unlocked_at) PK (user_id, unlock_tid). 재검사 없음
슬롯 WorkSlotTable.WorkSlotTID(=칸 번호 0~7) → UnlockTID. 0은 항상 열림 = 시작 2칸. 행 수 = 상한 8
계정 레벨 아직 서버에 없다. AccountLevel은 전부 0이라 이번엔 검사만 자리를 만들고 값은 0 비교로 통과한다

할 일

  • [x] UnlockCatalog — UnlockTable·WorkSlotTable 인덱스 (IndustryLevelCatalog와 같은 모양, GameServer에서 LoadAll) + 로드 검증 3종 (해금 2.5): 선행 순환·자기 참조 → 예외 · 한 UnlockTID를 두 콘텐츠가 참조 → 예외 · 미참조 → 경고
  • [x] t_user_unlock DDL (sqlite-sql-creator) + 로그인 적재(LoginRepository) + SaveUnlockRepository
  • [x] User.Unlock.cs — IsUnlocked(unlockTid) (0은 true) · TryUnlock(unlockTid, currency) 판정 순서대로 (고른 재화가 이 해금에 없으면 UnlockLocked) · GrantUnlock(unlockTid) — 조건·차감 없이 기록·통지·콘텐츠 후속 (퀘스트·튜토리얼·치트용, 해금 2.4). 치트 Unlock = 7도 이때 붙인다
  • [x] 패킷 (packet-creator) — C_UnlockRequest{UnlockTID, Currency} · S_UnlockResponse{Result, UnlockTID} · S_UnlockListResponse{UnlockTIDs}(로그인 직후, 슬롯 스냅샷보다 앞)
  • [x] EResultCode — InvalidUnlockTID · AlreadyUnlocked · UnlockLocked. NotEnoughCurrency는 있다
  • [x] 슬롯 후속 — 해금된 UnlockTID가 WorkSlotTable의 어느 칸인지 찾아 WorkStation.Unlock → S_WorkStationSlotSyncResponse 푸시
  • [x] 로그인 시 열린 칸 구성 — WorkSlotTable을 돌며 IsUnlocked인 칸만 만든다. DefaultSlotCount 상수 제거. DB에 잠긴 칸의 배치 행이 있으면 경고 후 무시 (지우지 않는다)
  • [x] 배치 검증에 상한 — 없는 칸·잠긴 칸은 InvalidSlotIndex (지금 코드가 이미 TryGet 실패로 거절한다 — 확인만)
  • [x] 테스트 (server-tdd) — 선행 미충족 거절 · 골드 부족 거절(차감 없음) · 정상 해금 시 골드 차감 + 기록 + 슬롯 생성 · 이미 열린 것 거절 · 재로그인 후 유지
  • [x] 프로토콜 미러 커밋 (서버 일감 공통 완료 조건)

완료 조건

신규 계정은 2칸으로 시작하고, 로그인 직후 받은 열린 목록과 테이블만으로 클라가 8칸 각각이 열렸는지·무엇이 필요한지를 그릴 수 있으며, 해금 요청을 보내면 서버가 조건을 검증해 골드를 빼고 칸을 열거나 사유를 담아 거절한다. 재로그인해도 유지된다.

막고 있는 것 / 선행 일감

  • 없음. 데이터·규칙이 다 있다.
  • 계정 레벨 요구치(T-012)는 값만 나중에 채운다 — 검사 코드는 이번에 넣는다.

이어지는 일감

  • T-039 — 클라 잠긴 칸 표시·해금 UI (조건 표시는 테이블로 지금 가능, 요청·목록은 이 일감 뒤)
  • T-021 — 산업 레벨을 같은 시스템으로 이관 (계정 레벨 뒤)

관련 커밋

  • 39ca310 — feat: 해금 시스템 서버 구현 — 첫 적용은 작업슬롯

참고

  • 해금 기획 — 2장 판정 순서 · 6장 패킷·DB
  • GameDesign/Excel/Unlock.xlsx · Server/GameData/Tables/UnlockTable.cs · WorkSlotTable.cs
  • Server/WSGameServer/User/User.WorkStation.cs · User.DB.cs · User.Currency.cs(TrySpendGold)