최종 갱신 (KST)

← 일감 목록
T-017

산업 레벨 구현 — 엑셀 · DB · 패킷 · 서버

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

배경

산업 레벨을 실제로 굴리려면 엑셀·DB·패킷·서버 로직이 전부 한 칸씩 늘어난다. 슬롯 배치가 (산업, 캐릭터)에서 (산업, 레벨, 캐릭터)가 되고, 드롭 롤의 대상이 시트 전체가 아니라 (산업, 레벨)로 걸러진 행 목록이 된다.

해금 조건이 정해지기 전에는 시작할 수 없다 → ✅ 2026-08-01에 조건이 확정됐다 — 적성 N + 계정 레벨 M (AND). 컬럼은 RequiredAptitude + RequiredAccountLevel 정수 2개로 끝난다. 범용 조건 시스템(ConditionType enum 등)을 만들지 않는다.

✅ 2026-08-01에 레벨 개수(5개)와 판정 비용 구조도 확정됐다. 판정 1회 비용이 전역 상수에서 (산업, 레벨)의 필요 점수가 된다 — Lv1 30,000에서 레벨당 ×3. 점수는 작업속도로 쌓인다(적성 1 = 1000점/초).

⚠️ 남은 선행 조건: 요구치 값(N·M) — 엑셀에 임시값이 들어가 있다 (T-016).

🔴 지금 서버가 잘못 동작하고 있다 (2026-08-02 확인 → 드롭은 2026-08-10 해소)

엑셀만 먼저 레벨별로 갈라져서, 레벨을 모르는 옛 코드가 새 데이터를 읽고 있다. 이건 "아직 안 됨"이 아니라 "틀리게 됨" 이다.

증상 원인 상태
어떤 레벨을 돌리든 Lv1~Lv5 아이템이 전부 섞여 나온다 DropTableCatalog.LoadAll이 38행을 통째로 등록했다 ✅ 해소 (2026-08-10) — 키를 (ItemType, Level)로 갈라 25개 테이블로 등록
희귀도 분포가 의도와 다르다 5개 레벨의 가중치가 한 통에 합산돼 총합이 부풀었다 ✅ 해소 — DropSheetTest가 (산업, 레벨)별 분포를 검증한다
판정 주기가 레벨과 무관하게 30초 고정 WorkStationSlot.JudgeCost가 상수 30_000_000 ✅ 해소 (2026-08-10) — IndustryLevelCatalog가 RequiredScore × 1000(초→ms 환산)을 준다
  • WorkStationSlot에 IndustryLevel·JudgeCostUnits 필드가 생겼다 (2026-08-10 · 기본 Lv1 · 30초). 다만 DB·패킷이 없어 항상 Lv1로 적재된다 — 현재 해금이 Lv1뿐이라 기획과는 맞는다.
  • 레벨 선택 수단(DB·패킷·해금)이 없어 상위 레벨은 아직 돌 수 없다. T-009(회당 산출)의 레벨별 실측은 그것이 붙어야 가능하다. (Lv1 실측·T-008 분포는 가능해졌다)

할 일

엑셀 — ✅ 완료 (2026-08-01)

  • [x] IndustryLevelTable 시트 신설 (GameDesign/Excel/Industry.xlsx · 25행) — IndustryLevelTID(키) · IndustryType · Level · Name · RequiredScore · RequiredAptitude(0~10) · RequiredAccountLevel · Description
  • [x] <산업>BasicTable 5종에 IndustryLevel 컬럼 추가 + 레벨별 행 확장 (산업당 38행 · 총 190행)
  • [x] IndustryLevel의 Ref 검사 방식 → IndustryLevelTable.Level로 그대로 걸린다. ReferenceValidator는 참조 대상이 키가 아니어도 값 집합을 만들어 검사한다
  • [x] 신규 아이템 120종 추가 (Lv2~5 × 희귀도 6 × 산업 5) — ItemTable 총 150행 (가챠 전용 6종을 더해 시트는 156행)
  • [x] generate-tables.ps1 실행 — .bytes·DataLog·Unity 미러까지 생성 완료

2026-08-08 엑셀 재편 (레벨 30~40 확장 대비). Drop.xlsx → 산업별 5개 파일(파일 안은 레벨별 시트), Industry.xlsx는 산업별 시트로 분리 — 제너레이터가 병합하므로 테이블명· 생성물·서버 코드 영향 없음. DropTID는 산업×100000+레벨×100+순번으로 재채번 (산업 자리 = ItemType enum 값 — 농사 1 · 낚시 2 …). DropTID는 DB 미저장이라 안전하다.

DB — ✅ 완료 (2026-08-10)

  • [x] t_user_workstation_slot에 industry_level 컬럼 추가 (NOT NULL DEFAULT 1)
  • [x] t_user_industry_level 신설 — 유저 × 산업 → 최대 해금 레벨 (STRICT · PK (user_id, industry))
  • [x] 기존 슬롯 행의 마이그레이션 — DEFAULT 1이 채운다. 기존 4행 보존 확인

⚠️ 이 저장소는 스키마 파일이 없다. Server/Shared/game.sqlite3의 DDL 텍스트가 곧 문서라, ALTER TABLE ADD COLUMN이 컬럼을 주석 앞에 끼워 넣어 주석이 어긋난 뒤 테이블을 새로 만들어 데이터를 이관했다(컬럼 순서·주석 정합). 다음에 컬럼을 더할 때도 같은 절차를 쓴다.

패킷 · 서버

  • [x] WorkStationSlotInfo에 IndustryLevel 추가 — ✅ 완료 (2026-08-10) · Unity 미러까지 반영
  • [x] 슬롯 배치 요청에 레벨 추가 · 서버가 해금 여부를 검증한다 — ✅ 완료 (2026-08-10). C_WorkStationAssignRequest.IndustryLevel(byte) 추가 · EResultCode.IndustryLevelLocked = 203 신설. 저장된 해금 레벨(t_user_industry_level)과 대조하고 하한(1 미만)도 막는다. ⚠️ 레벨을 올려 주는 판정 로직이 아직 없어 모두 Lv1이다 — 상위 레벨은 지금 전부 거절된다 > 구 클라이언트 호환: 레벨 필드를 안 채우면 0이 오는데, 핸들러가 이를 "미지정"으로 보고 > 기본 레벨로 돌린다. 기본 레벨은 항상 열려 있어 우회가 아니다
  • [x] 적성 0 캐릭터 배치를 거절하는 응답을 함께 넣는다 — ✅ EResultCode.NoAptitude (미보유는 CharacterNotOwned로 구분). 테스트 적성_0인_산업_배치는_NoAptitude로_거절한다
  • [x] WorkStationSlot.JudgeCost 상수를 (산업, 레벨) 조회로 바꾼다 — ✅ 완료 (2026-08-10). IndustryLevelCatalog 신설(RequiredScore × 1000 — 엑셀은 초, 서버는 밀리초). 슬롯이 인스턴스 JudgeCostUnits를 갖고 Assign·로그인 적재가 테이블 값을 넣는다
  • [x] WorkStationSlotInfo.JudgeCostUnits를 그 값으로 채운다 — ✅ ToInfo()가 인스턴스 값을 보낸다
  • [x] 🔴 드롭 롤을 (산업, 레벨) 인덱스 기준으로 변경 — ✅ 완료 (2026-08-10). DropTableCatalog 키를 (ItemType, Level)로 확장, 로드 시 IndustryLevel로 갈라 25개 테이블 등록. WorkStationSlot.IndustryLevel 추가(기본 Lv1 · DB 미저장), WorkStation.Settle이 슬롯의 레벨로 조회. 검증: DropSheetTest(25조합 등급·분포)
  • [x] 레벨 변경 시 변경 직전에 정산 — ✅ 산업 변경과 같은 경로(AssignWorkStation)를 탄다. 테스트 레벨을_바꾸기_전_구간은_이전_레벨의_비용으로_정산된다가 잠갔다 (Lv1 60초 = 2판정 / 새 Lv2 비용으로 계산하면 0판정이라 어긋나면 즉시 빨개진다)
  • [x] 테스트 — 해금하지 않은 레벨 배치 거절 · 레벨 변경 시 소급 없음 ✅ (전체 186건 통과. DropSheetTest 25조합 · IndustryLevelCatalogTest · 배치 검증 7건)

해금 판정은 T-021로 분리했다 (2026-08-10). t_user_industry_level을 올려 주는 로직(적성 N + 계정 레벨 M 비교)과 그 시점 연결, "캐릭터를 잃어도 열린 레벨 유지" 테스트가 그쪽으로 갔다. 요구치 N·M이 임시값(T-016 미완)이라 지금 만들어도 밸런스 검증이 안 되기 때문이다.

완료 조건

해금하지 않은 레벨로는 배치가 거절되고, 레벨을 바꾸면 그 시점 이전 구간이 이전 레벨의 필요 점수·드롭 테이블로 정산되며, 레벨마다 판정 주기와 나오는 아이템이 실제로 다른 상태. → ✅ 달성 (2026-08-10). 셋 다 테스트로 잠갔다.

⚠️ 단, "실제로 다르다"는 구조까지만 확인했다. 상위 레벨을 열 수단(해금 판정)이 없어 Lv2 이상을 실제로 돌려 보지는 못했다 — 실물 확인은 T-021의 완료 조건이다.

막고 있는 것 / 선행 일감

  • T-016 해금 조건 ✅ 풀렸다
  • 레벨 개수 ✅ 5개로 확정
  • 엑셀 작업 ✅ 완료 (2026-08-01)
  • T-016의 남은 것(요구치 N·M) → T-021로 넘어갔다. 이 일감은 요구치와 무관하게 끝난다
  • T-005와 같은 시트를 건드린다 — 시트 작업이 끝나 충돌 없음
  • 이 일감이 T-008·T-009를 막고 있다 → T-008(Lv1 분포)은 풀렸다. T-009의 레벨별 실측은 T-021로 넘어갔다

관련 커밋

  • 없음 (미커밋 — 커밋 후 해시를 여기에 적는다)