08. 캐릭터 (Character)
상위 문서:
게임기획코어.md· 전제:작업슬롯상태: 적성·장비·장비 인챈트·시작 캐릭터·획득 경로(가챠 — 캐릭터·장비)·경험치 획득·레벨 곡선·레벨 효과(작업속도 가산) 확정 바뀌면 갱신:거래·게임UI·게임기획코어·기획평가·산업레벨·아이템·자원채취·작업슬롯진행 및 성장·특성·해금
슬롯에 배치되어 실제로 일하는 주체다. 섬 컨셉에서는 섬에서 일하는 일꾼이며, 플레이어 자신(섬주인)은 캐릭터가 아니다 → 3장.
캐릭터가 정해지지 않으면 작업슬롯의 속도를 계산할 수 없다. 이 문서는 구현이 이미 끝난 부분을 확정으로 옮겨 적고, 남은 미정을 명시한다.
1. 확정 사항
| # | 항목 | 확정 내용 |
|---|---|---|
| 1 | 캐릭터 스탯 = 산업 적성 | 별도 스탯을 두지 않는다. 5개 산업 각각에 대한 적성(0~10)이 전부다 |
| 2 | 적성 = 기본 작업속도 | 적성을 WorkSpeedTable로 변환한 값이 속도 계산의 출발점이다 |
| 3 | 적성 0 = 배치 불가 | 그 산업을 다루지 못한다. 배치 요청을 서버가 거절한다 → 2.2 |
| 4 | 적성 = TID별 고정값 | CharacterTable 값 그대로다. 레벨·장비는 적성을 바꾸지 않고 속도식의 가산 항으로만 기여한다. 클라이언트에 내려가는 값은 서버가 준다 → 5.4 · 7장 |
| 5 | 슬롯당 1명 | 여러 명을 한 슬롯에 넣지 않는다 |
| 6 | 장비 부위 = 무기1 · 장신구2 · 보석1 | 4칸. 효과는 속도식의 가산 항 — 대상 산업 지정(None=전 산업) + 가산 천분율. 장비는 적성을 바꾸지 않는다. 서버 구현 완료(2026-09-17 · T-002) · 정의 69종 입력(2026-09-19) → 1.3. 획득 경로 = 장비 뽑기(무기·장신구·보석 풀 3개 → 5.1.1). 장비 개체마다 인챈트(등급 하나 + 장비 등급별 칸 1~3개 — 속도 가산·캐릭터 경험치)가 붙는다 → 1.4 |
| 7 | 계정(섬주인)과 분리 | 섬주인은 캐릭터가 아니다. 배치되지 않는다 → 3장 |
| 8 | 종족을 여러 개 둔다 | 사람·몬스터 등. 외형과 적성 분포의 차이로만 표현한다 → 4장 |
| 9 | 획득 경로 = 가챠 | 골드를 내고 캐릭터 풀을 뽑는다 → 5.1 |
| 10 | 등급 = GlobalRarity 재사용 |
아이템과 같은 6단계 축. 등급이 곧 최고 적성의 상한이다 → 1.2 |
| 11 | 경험치 = 판정 1회마다 획득 | 배치된 슬롯에서 판정이 성립할 때마다 그 (산업, 레벨)의 ExpPerJudge만큼. 만렙 100, 곡선은 CharacterLevelTable → 5.2 |
| 12 | 레벨 효과 = 작업속도 가산 | 레벨이 오르면 자동으로 빨라진다 — CharacterLevelTable.SpeedAddPermille(초안 레벨당 +1%). 고를 것·되돌릴 것이 없다 (2026-10-06 · 이슈 #35) → 5.4 |
적성이 있어서 배치가 퍼즐이 된다. 슬롯 3개에 캐릭터 5명이면 "누구를 어디에"가 실제 선택이 된다. 적성이 없으면 스탯 높은 순으로 채우면 끝이라 배치에 의사결정이 없다.
1.1 개체와 정의를 구분한다
같은 캐릭터(TID)를 여러 장 가질 수 있다.
| 무엇 | 어디 | |
|---|---|---|
CharacterTID |
캐릭터 종류 — 이름·적성·종족 | 엑셀 Character.xlsx |
character_id |
캐릭터 개체 — DB 발급 PK | t_character |
| 개체가 갖는 것 | 성장의 입력(레벨·경험치) | t_character |
기본 적성을 DB에 저장하지 않는다. 저장하면 엑셀에서 밸런스를 조정해도 DB가 낡은 값을 붙든다.
DB에 남는 것은 레벨·경험치뿐이고, 레벨 가산은 조회 때마다 CharacterLevelTable에서 읽는다 → 5.4.
중복은 개체를 늘리는 것으로 끝난다 (2026-09-02 확정). 조각·강화로 바꾸지 않고,
보유 정원도 두지 않는다. 둘 다 되돌리기 어려운 결정이 아니므로 가장 단순한 쪽에서 시작한다 —
정원이 필요해지는 시점은 목록 UI가 감당하지 못할 때이고, 그때 정하면 된다.
남는 개체의 출구는 즉시 판매(CharacterTable.BasePrice)와 경매장이다(2026-10-01 · #47) → 거래 3.1 · 4.2.
슬롯에 배치됐거나 장비를 낀 캐릭터, 마지막 남은 캐릭터는 팔거나 올릴 수 없다.
구현:
Server/WSGameServer/User/Character/Character.cs
1.2 등급 — 최고 적성의 상한 (2026-09-02 확정)
CharacterTable.GlobalRarity는 아이템과 같은 6단계 축을 쓴다.
등급이 정하는 것은 적성의 총합이 아니라 최댓값이다 — 슬롯 하나에 한 명이 들어가므로
실제로 체감되는 값은 그 슬롯 산업의 적성 하나뿐이다.
| 등급 | 최고 적성 | 종수 | 성격 |
|---|---|---|---|
| Common | 1~2 | 6 | 입문 — 시작 캐릭터(1001) 포함 |
| Uncommon | 3~4 | 8 | 산업 담당 |
| Rare | 5~6 | 7 | 산업 특화 |
| Epic | 7 | 5 | 산업 정예 |
| Legendary | 9 | 3 | 단일 산업 최상위 |
| Mythic | 7 (전 산업) | 1 | 만능형 — 어디든 꽂히지만 최고는 아니다 |
Mythic이 전 산업 1위가 아닌 이유 — 한 명이 모든 산업에서 최고면 뽑는 순간 배치가 "그 캐릭터부터 채우기"로 수렴해 배치 퍼즐이 무너진다(1장 각주). 만능형은 빈 칸을 메우는 가치를, 전설 특화는 그 산업을 가장 빠르게 도는 가치를 갖는다.
⚠️ 수치는 전부 테스트용이다. 등급별 확률·적성 곡선은 희귀도 분포(T-008)· 회당 산출(T-009)이 정해진 뒤에 다시 잡는다.
이 표는 적성의 가이드다. 적성은 성장으로 바뀌지 않는다 — 레벨은 속도 가산으로만 기여한다 → 5.4.
1.3 장비 사다리 (2026-09-19 입력 · 69종)
부위가 무엇을 맡는가를 갈랐다 — 칸 구성(무기1 · 장신구2 · 보석1)이 그대로 축이 된다.
| 부위 | 대상 | 종수 | 역할 |
|---|---|---|---|
| 무기 | 산업 전용 (5산업 × 6등급) | 31 | 그 산업에 특화시킨다. 가장 큰 가산 |
| 장신구 | 전 산업 (6등급) | 7 | 두 칸을 어디에나 쓴다. 산업을 안 가리는 대신 작다 |
| 보석 | 산업 전용 (5산업 × 6등급) | 31 | 무기와 같은 축의 보조. 무기의 40% 수준 |
가산 천분율(‰) — 기본 작업속도가 적성 1 = 1000‰, 적성 10 = 4000‰인 위에 더해진다(2.1):
| 등급 | 무기(전용) | 보석(전용) | 장신구(전 산업) |
|---|---|---|---|
| Common | 100 | 40 | 30 |
| Uncommon | 180 | 70 | 55 |
| Rare | 300 | 120 | 90 |
| Epic | 480 | 190 | 145 |
| Legendary | 720 | 290 | 215 |
| Mythic | 1000 | 400 | 300 |
- 한 벌을 다 맞추면 Common 260‰ · Mythic 2000‰(무기 1000 + 장신구 300×2 + 보석 400)이다. 적성 1 캐릭터는 3배가 되고 적성 10 캐릭터는 1.5배가 된다 — 장비는 낮은 적성을 끌어올리는 축이고, 높은 적성을 더 벌리는 축이 아니다. 배치 퍼즐(1.2의 Mythic 각주)을 장비가 무너뜨리지 않게 한 것이다.
- 전 산업 입문 장비 3종(목검 · 구리 반지 · 원석)은 전용의 절반 수준으로 남겼다 — 산업을 정하기 전의 임시 장비다.
- 가격은 아이템과 같은 등급 배율(
10 · 25 · 60 · 150 · 400 · 1000)을 따른다 → T-018과 함께 본다.
⚠️ 수치는 전부 테스트값이다. 확정은 희귀도 분포(T-008)·회당 산출(T-009)·
BasePrice(T-018)와 함께 잡는다 — 장비 가산은 시간당 수익에 직접 곱해지므로 따로 정하면 그 셋이 어긋난다. ⚠️ 구 번호 둘(1002낚싯대 ·2002어부의 목걸이)은 대역 규칙 밖에 있다. DB에 살아 있을 수 있어 지우지 않았다.
1.4 장비 인챈트 (2026-10-01 개편 · #46)
장비 개체 하나에 붙는 추가옵션이다. 같은 EquipTID라도 개체마다 인챈트가 다르다 —
장비 정의(1.3)는 그대로 두고, 개체가 (인챈트 등급, 능력치 칸) 을 갖는다.
| 항목 | 규칙 |
|---|---|
| 칸 수 | 장비 등급이 정한다 — 일반·고급 1칸 · 희귀·영웅 2칸 · 전설·신화 3칸 (EnchantGradeTable.SlotCount) |
| 인챈트 등급 | 장비 하나에 하나. 일반 ~ 신화 6단계(GlobalRarity 재사용) + 인챈트 없음. 모든 칸이 이 등급의 옵션을 쓴다 |
| 수치 | 등급 × 옵션 종류마다 고정값이다. 범위 굴림이 없다 |
| 효과 축 | 속도 가산(대상 산업 지정, None = 전 산업) · 캐릭터 경험치(천분율) 둘. 획득 아이템의 양·질에는 관여하지 않는다 → 자원채취 2.1 |
| 중복 칸 | 허용한다. 두 칸이 같은 옵션이면 효과도 두 번 붙는다 |
| 착용 중 | 큐브를 쓸 수 없다(EnchantEquipped). 캐릭터에서 벗겨야 한다 |
큐브 1회 — 성공하든 실패하든 큐브는 소모된다.
| 장비 상태 | 결과 |
|---|---|
| 인챈트 없음 | 일반 등급으로 시작해 칸 수만큼 뽑는다 |
| 인챈트 있음 | 큐브 확률로 등급이 한 단계 오른다(내려가지 않는다). 오르든 안 오르든 칸 전부를 그 등급에서 다시 뽑는다 |
- 칸은 그 등급의
EnchantOptionTable행들에서Weight비례로 뽑는다. - 신화는 오를 곳이 없다 — 판정 없이 다시 뽑기만 한다.
상급 큐브(B)는 이전 값을 유지할 수 있다 (#53) — 굴린 결과를 바로 덮어쓰지 않고 이전 값과 새 값을 나란히 보여 준 뒤 하나를 고른다. ❌ 미구현 (일감 T-127)
| 항목 | 규칙 |
|---|---|
| 고르는 단위 | 등급 + 칸 전부를 한 묶음으로 고른다. 이전 값을 고르면 등급 상승도 함께 되돌아간다 |
| 소모 | 고르기 전에 이미 소모된다 — 어느 쪽을 골라도 큐브는 돌아오지 않는다 |
| 고르기 전 | 그 장비는 장착 · 큐브 · 판매 · 경매 등록을 할 수 없다 |
| 고르지 않고 접속이 끊기면 | 이전 값을 유지한다 ⚠️ 제안 |
| 첫 사용 | 이전 값 = 인챈트 없음. 그대로 두는 것도 고를 수 있다 |
| 큐브 A | 지금처럼 바로 덮어쓴다 |
등급 상승 확률 (테스트값) — 큐브 A는 EnchantGradeTable.UpPermyriad(만분율) 그대로, 큐브 B는 EnchantItemTable.UpRatePermille로 ×1.25.
| 큐브 | TID | 일반→고급 | 고급→희귀 | 희귀→영웅 | 영웅→전설 | 전설→신화 |
|---|---|---|---|---|---|---|
| 인챈트 큐브 (A) | 100015 | 16% | 8% | 4% | 0.4% | 0.04% |
| 상급 인챈트 큐브 (B) | 100018 | 20% | 10% | 5% | 0.5% | 0.05% |
등급 상승 기대 큐브 수 — 한 단계 = 1 ÷ 확률. 누적은 첫 사용(인챈트 없음 → 일반) 1개를 포함해 그 등급에 닿을 때까지다.
| 큐브 | →고급 | →희귀 | →영웅 | →전설 | →신화 | |
|---|---|---|---|---|---|---|
| 인챈트 큐브 (A) | 한 단계 | 6.25 | 12.5 | 25 | 250 | 2,500 |
| 누적 | 7.25 | 19.75 | 44.75 | 294.75 | 2,794.75 | |
| 상급 인챈트 큐브 (B) | 한 단계 | 5 | 10 | 20 | 200 | 2,000 |
| 누적 | 6 | 16 | 36 | 236 | 2,236 |
- 확률을 바꾸면 이 표도 함께 고친다. 가격(
BasePrice)은 따로 바뀌므로 골드 환산은 적지 않는다.
옵션 수치 (테스트값, %)
| 옵션 | 일반 | 고급 | 희귀 | 영웅 | 전설 | 신화 |
|---|---|---|---|---|---|---|
| 속도 · 특정 산업 | 1 | 2 | 4 | 9 | 18 | 25 |
| 속도 · 전 산업 | 0.5 | 1 | 2 | 5 | 10 | 14 |
| 캐릭터 경험치 | 1 | 2 | 3 | 7 | 15 | 20 |
효과가 붙는 곳
| 옵션 | 어디에 | 합성 |
|---|---|---|
속도 (Speed) |
착용한 캐릭터가 배치된 슬롯 — 옵션의 대상 산업이 슬롯 산업과 같거나 None 인 칸만 |
장비 기본 가산과 함께 속도식의 가산 항으로 더해진다 → 작업슬롯 3.4 |
캐릭터 경험치 (CharacterExp) |
착용한 캐릭터가 판정마다 받는 경험치 | ExpPerJudge × (1 + Σ칸). 계정 경험치는 캐릭터 경험치 전량이라 함께 오른다 → 5.2 · 특성 3장 |
- 얻는 곳 = 상자 개봉 — 나무·은·황금 × 산업 Lv1~5, 15종(
GachaItemTable풀 9~23). 수량은 상자의 레벨만큼 → 아이템 4.1. - 착용 중 거부가 속도 정산을 단순하게 만든다. 인벤토리에 있는 장비만 바뀌므로 큐브가 가동 중인 슬롯의 속도·경험치를 바꿀 수 없다. 바뀐 값은 다음에 장착할 때 장착 경로가 반영한다.
⚠️ 확률·수치·
Weight는 전부 테스트값이다. 칸 고정(잠금)·인챈트 이전은 기획에 없다.
2. 적성 → 작업속도
2.1 변환은 테이블이 한다
적성(0~10)을 WorkSpeedTable.BaseWorkSpeedPermille(천분율)로 변환한다.
변환식을 코드에 두지 않는다 — 이 값이 재화 생성량에 직접 곱해지므로, 엑셀 수정만으로
밸런스가 조정돼야 한다.
| 적성 | 기본 작업속도 | 실효 주기 (보정 없을 때) |
|---|---|---|
| 0 | 0 |
배치 불가 |
| 1 | 1000 (1.0배) |
30.0초 — 기준 |
| 5 | 2100 (2.1배) |
14.3초 |
| 10 | 4000 (4.0배) |
7.5초 — 상한 |
여기에 특성·장비·부스트가 가산으로 붙는다:
속도 = 적성기본값 × (1 + Σ가산) × Π승산 → 작업슬롯 3.4
2.3 적성은 속도에만 작용한다 (2026-09-14)
적성은 산업 레벨 해금 조건이 아니다. 산업 레벨은 개척 특성의 레벨(특성 포인트 + 계정 레벨)로 연다 → 특성 2.2. 적성이 정하는 것은 "이 슬롯이 얼마나 빠른가" 하나다.
가챠 천장·확정 지급은 여전히 미정이지만(5.3), 콘텐츠 해금과는 무관해졌다.
2.2 적성 0 = 배치 불가 (2026-08-01 재확정)
적성 0인 산업에는 캐릭터를 배치할 수 없다. 서버가 배치 요청 자체를 거절한다.
⚠️ 2026-07-30에 문서상 "배치는 되고 매우 느리다"로 바꿨다가 2026-08-01에 원복했다. 그 변경은 문서에만 적혔고 구현된 적이 없다 — 코드(
Character.CanWork)와 엑셀(WorkSpeedTableTID 0 =0)은 처음부터 "배치 불가"였다. 이번 결정으로 문서가 코드·데이터와 다시 일치한다. 엑셀 수정은 필요 없다.
왜 되돌렸는가 — "할 수는 있는데 손해다"는 선택지를 늘리는 것처럼 보이지만, 실제로는 적성이라는 축을 무의미하게 만든다. 아무 캐릭터나 아무 데나 놓을 수 있으면 배치 퍼즐이 "속도 순 정렬"로 수렴한다. 못 하는 것이 있어야 누구를 어디에 둘지가 문제가 된다.
- 속도 0은
TimeUntilNextJudge에서 0으로 나누게 된다. "정지"는 배치를 비우는 것으로 표현한다. - 슬롯이 남는데 적성 맞는 캐릭터가 없는 상황은 캐릭터 획득(5.1)이 푸는 문제이지, 적성 규칙을 느슨하게 해서 풀 문제가 아니다.
3. 계정(섬주인)과의 분리 (2026-08-01 확정)
플레이어 자신은 캐릭터가 아니다. 섬주인은 일하지 않는다 — 일은 캐릭터가 한다.
| 캐릭터 | 계정(섬주인) | |
|---|---|---|
| 슬롯 배치 | 된다 | 안 된다 |
| 성장 축 | 캐릭터 레벨 | 계정 레벨 |
| 효과 | 그 슬롯의 작업속도 | 레벨형 특성 — 산업 레벨 해금(개척) · 산업별 속도 · 산출량 → 특성 |
| 획득 | 가챠 → 5.1 | 계정 그 자체 |
3.1 계정 경험치는 작업으로만 오른다 (확정)
캐릭터가 작업 → 캐릭터 경험치 ↑ ─(그대로)→ 계정(섬주인) 경험치 ↑ → 레벨마다 특성 포인트 1
자원을 먹여서는 계정 레벨이 오르지 않는다. 먹이기로도 오르면 골드로 계정 레벨을 사는 길이 열린다. 계정 레벨은 "캐릭터를 굴린 시간"에만 묶어 둔다.
| 항목 | 상태 |
|---|---|
| 계정 레벨의 존재 · 특성 포인트 공급 | ✅ 확정 |
| 계정 경험치 = 캐릭터가 얻은 경험치 전량 (비율 없음 · 캐릭터가 만렙이어도 쌓인다) | ✅ 확정 (2026-09-19) |
| 계정 레벨 곡선 | ✅ 테스트값 — 캐릭터 곡선 × 8, 100레벨 (AccountLevelTable) → 특성 3장 |
3.2 ⚠️ 자기가속 루프를 조심한다
골드 ↑ → 슬롯 ↑ → 채취 총량 ↑ → 골드 ↑ → …
게임기획코어 4장의 경고("총량 = 접속 시간 × 슬롯 수 × 속도 배수")에 번 것이 슬롯을 늘리는 되먹임이 붙는다.
처음에는 슬롯 확장을 레벨(상한) × 골드(구매)의 이중 게이트로 두어 제동하려 했으나, 계정 레벨도 슬롯이 버는 경험치로 오르므로 늦출 뿐 끊지 못한다 — 2026-09-30 골드 단독으로 바꿨다(#43). 제동은 슬롯 해금 골드 곡선이 맡는다 → 거래 3.3 · 일감 T-012
3.3 CharacterTID 1 폐기 · 영구 결번 ✅ 실행 완료 (2026-08-02)
TID 1 '유저 캐릭터'(전 산업 적성 1, 기본 지급)는 섬주인 개념이 들어오면서 자리를 잃는다.
섬주인은 배치되지 않으므로 캐릭터 테이블에 있을 이유가 없다.
- TID 1은 폐기하고 영구 결번으로 둔다. 다른 캐릭터에 1번을 다시 붙이지 않는다 (게임기획코어 4장 식별자 규칙).
- 2026-08-02 실행. 일반 캐릭터
1001~1006입력과 함께CharacterTable에서 행을 삭제했다. 2026-09-02에1007~1030을 더해 현재 테이블은1001~103030행이다 → 1.2 - 시작 캐릭터 =
1001램볼 ✅ 확정(2026-08-02).User.DefaultCharacterTid가 이 값을 쓴다.- 전 산업 적성 1로 둔다. 적성 0인 산업은 배치가 막히므로, 시작 캐릭터에 0이 섞이면 신규 유저가 그 산업을 아예 시작하지 못한다.
기존 계정은 다음 로그인에 스스로 복구된다. DB에 남은 TID 1 개체는
LoadCharacters가 경고를 남기고 건너뛰므로 보유 캐릭터가 0이 되고,OnLoginDataLoaded가 그때1001을 지급한다 — 신규 유저 전용 경로가 아니라 "캐릭터가 0명이면 지급" 이라 이관이 자동으로 끝난다.⚠️ 다만 고아가 된
t_character행 자체는 남는다. 지우지 않으면 그 계정은 로그인할 때마다 경고 한 줄을 남긴다(동작에는 지장 없다).
4. 종족
| 항목 | 내용 |
|---|---|
| 표현 범위 | 외형 + 산업 적성 분포의 차이로만. 예: 몬스터 계열은 사냥 적성이 높고 농사가 낮다 |
| 금지 | 종족 상성 · 종족 전용 스킬을 두지 않는다 |
종족을 별도 시스템 축으로 만들면 적성·장비·특성 위에 네 번째 밸런스 항이 생긴다. 종족 차이는 가챠 풀 안의 적성 분포로만 표현한다.
데이터 영향: Character.xlsx에 Race 컬럼 추가 · Enum.xlsx에 CharacterRace enum 신설.
enum은 뒤에만 추가한다.
⏸
Race컬럼은 아직 넣지 않았다(2026-09-02 재확인). 30종의 적성은 등급과 주력 산업으로 설계돼 있고(1.2), 종족 축은 아직 그 위에 얹히지 않았다. 외형 리소스가 붙을 때 함께 넣는다 — 지금 넣으면 값이 외형과 따로 놀다가 어긋난다.
5. 획득 경로 · 남은 미정
5.1 획득 경로 = 가챠 (2026-09-02 확정)
캐릭터는 가챠로 얻는다. 골드를 내고 캐릭터 풀을 뽑으며, 단차(1회)와 10연차 두 가지다.
| 항목 | 확정 내용 |
|---|---|
| 비용 | 골드 직접. 가챠 티켓을 만들지 않는다 → 거래 3.2 |
| 뽑기 단위 | 1회 · 10회. 값은 GachaInfoTable의 CostSingle·CostMulti |
| 풀 구성 | GachaCharacterTable — 30종 전체가 등급별 가중치로 들어간다 |
| 중복 | 개체를 늘린다. 정원 없음 → 1.1 |
시작 캐릭터(1001 램볼)도 풀에 들어 있다. 빼지 않는 이유는 등급 축이 이미 그것을
표현하기 때문이다 — 램볼은 Common이라 나와도 손해가 아니라 가장 흔한 결과다.
이 결정이 닫은 것 — 캐릭터가 1종이라 배치가 퍼즐이 아니던 상태가 풀린다. 적성 0인 산업을 가진 캐릭터가 실제로 손에 들어와야 배치 화면의 잠금 표시가 화면에서 검증된다.
5.1.1 장비 뽑기 (2026-09-19 확정)
장비도 가챠로 얻는다. 캐릭터 뽑기와 같은 규칙(골드 직접 · 1회/10회)이고, 풀을 종류별로 나눈다 — 원하는 부위를 골라 뽑게 하기 위해서다. 한 풀에 섞으면 보석이 필요한데 무기만 나오는 헛뽑기가 생긴다.
풀 (GachaId) |
담는 장비 | 비용 (1회 / 10회) |
|---|---|---|
| 3 무기 뽑기 | EquipKind.Weapon 31종 |
500 / 5,000 골드 |
| 4 장신구 뽑기 | EquipKind.Accessory 7종 |
300 / 3,000 골드 |
| 5 보석 뽑기 | EquipKind.Gem 31종 |
200 / 2,000 골드 |
- 풀 구성은
GachaEquipTable. 가중치는 등급별(Common 640 · Uncommon 200 · Rare 100 · Epic 40 · Legendary 15 · Mythic 5) — 테스트값 - 한 풀에는 한 종류만 둔다 — 서버 테스트(
GachaEquipSheetTest)가 지킨다 - 중복은 개체를 늘린다(캐릭터와 같다). 인벤토리 칸 한도는 T-063
- 상자 풀에도 장비가 들어 있다 — 등급 = (상자 레벨 − 1) + 단계(나무 0 · 은 1 · 황금 2) → 아이템 4.1
5.2 성장 — 경험치 · 레벨 (2026-09-13 확정)
캐릭터는 일해서만 자란다. 배치된 슬롯에서 판정이 1회 성립할 때마다 경험치를 얻고, 그 레벨의 필요 경험치를 채우면 레벨이 오른다. 획득 경로는 지금 이것 하나뿐이다.
| 항목 | 확정 내용 |
|---|---|
| 획득 시점 | 판정 1회 성립 — 누적 점수 ≥ RequiredScore (산업 레벨 2.4) |
| 획득량 | IndustryLevelTable.ExpPerJudge — (산업, 레벨)별 값. Lv1 1 → Lv5 81 (레벨당 ×3). 착용 장비의 인챈트 경험치 칸이 있으면 × (1 + Σ칸)(내림) → 1.4 |
| 레벨 곡선 | CharacterLevelTable.RequiredExp — 그 레벨에 도달하는 데 직전 레벨에서 필요한 경험치. Lv2 10에서 레벨당 ×1.2 (내림) |
| 만렙 | 100 = 테이블의 마지막 행. 만렙에서는 경험치를 더 쌓지 않는다 |
| 레벨업 처리 | 필요치를 채우면 레벨 +1, 남은 경험치는 이월한다. 한 정산에서 여러 레벨이 오를 수 있다 |
| 저장 | t_character.level · t_character.exp — exp는 현재 레벨에서 쌓은 양(누적이 아니다) |
| 등급·산업 차등 | 없다. 어떤 캐릭터든 같은 판정·같은 장비면 같은 경험치 |
ExpPerJudge가 RequiredScore와 같은 ×3 등비인 이유 — 점수 30,000당 경험치 1로 고정되어
시간당 경험치가 산업 레벨 선택과 무관해진다. 상위 레벨은 판정이 1/3로 드물지만 한 번에 3배를 주므로
"경험치 때문에 Lv1만 돌린다"는 경로가 생기지 않는다. 시간당 경험치를 정하는 것은
작업속도(적성·보정)와 인챈트 경험치 칸뿐이다 — 적성 1이 시간당 120, 적성 10이 480(인챈트 없음 기준).
| 레벨 | 필요 경험치 | 누적 |
|---|---|---|
| 2 | 10 | 10 |
| 10 | 42 | 203 |
| 50 | 63,197 | 379,110 |
| 100 (만렙) | 575,124,823 | 3,450,748,839 |
경험치 1 = Lv1 판정 1회. 누적은 곧 "Lv1 환산 판정 횟수"다 — 만렙 100은 사실상 닿지 않는 상한이다. 누적값은 int 범위를 넘지만 저장·비교는 레벨별
RequiredExp(최대 5.75억)로만 하므로 int로 충분하다. ⚠️ 수치는 전부 테스트용이다. 도달 목표(진행 및 성장 2장)에서 역산해 다시 잡는다.
✅ 서버 구현 (2026-09-13).
SettleWorkStation이 판정 횟수 ×ExpPerJudge(× 인챈트 경험치 가산User.GetEquipExpAdd)를 배치 캐릭터에 더하고 (Character.GainExp·CharacterLevelCatalog), 확정값을t_character에 저장한 뒤S_CharacterSyncResponse로 그 개체를 밀어 준다. ⏸ 클라이언트는 아직 이 푸시를 화면에 반영하지 않는다 —PlayerDataModel배선은 클라 몫이다(T-003).
5.4 성장 → 작업속도 가산 (2026-10-06 확정)
캐릭터 레벨이 올리는 것은 작업속도다. 레벨이 오르면 자동으로 그 캐릭터가 빨라진다. 적성은 그대로다 — 레벨은 속도식(2장)의 가산 항으로 들어간다.
속도 = 적성기본값(WorkSpeedTable) × (1 + 장비 + 특성 + 레벨) × 전역배수
| 항목 | 확정 내용 |
|---|---|
| 원본 | CharacterLevelTable.SpeedAddPermille — 그 레벨의 가산 총량(천분율, 누적값). 초안 레벨당 +10‰ → Lv1 0 · Lv100 990‰ |
| 합성 | 장비·특성 가산과 더한 뒤 적성 기본값에 한 번 곱한다 — 적성이 높은 캐릭터일수록 같은 %에서 더 많이 얻는다 |
| 시점 | 레벨이 오르면 정산 → 속도 재계산 (작업슬롯 3.4의 순서 불변식). 새 속도는 오르기 전 구간에 소급되지 않는다 |
| 선택 · 되돌리기 | 없다. 결정할 것이 없으므로 후회할 것도 없다 |
| 개체 단위 | 같은 TID를 여러 장 가지면 개체마다 레벨이 따로이고 가산도 따로다 |
| 클라 | 가산값은 GameData 미러(CharacterLevelTable)에서 읽는다 — 별도 패킷이 없다. 슬롯 확정 속도는 CurrentWorkSpeed로 온다. 찍는 화면(게임UI 6장 #14)은 없어졌다 |
⚠️ 수치는 테스트용이다. 100행이라 레벨 구간마다 다른 기울기도 데이터만으로 바꿀 수 있다.
왜 적성 포인트를 버렸나 (2026-09-17 → 2026-10-06)
처음에는 10레벨마다 적성 포인트 1개를 플레이어가 산업 하나에 찍는 방식으로 확정했고 서버까지 구현했다. 클라 화면을 붙이기 전에 이슈 #35에서 되돌렸다.
- 방치형인데 능동 관리를 요구했다. 개체가 늘수록 찍을 포인트가 쌓이고, 방치하면 손해 보는 자원이 생긴다.
- 설명할 규칙이 많았다. 획득 주기 · 산업별 적성 · 캐릭터·산업마다 다른 상한 · 되돌리기 없음.
- 상한 150칸(30종 × 5산업)을 손으로 조율해야 배치 퍼즐이 지켜졌다. 포인트가 적성을 정수로 평평하게 올려 캐릭터 간 격차를 좁히기 때문이다. 가산 방식은 적성에 곱해지므로 격차가 오히려 유지된다.
철거한 것: CharacterLevelTable.AptitudePoint · CharacterTable …Cap 5컬럼 · t_character …_bonus 5컬럼 ·
패킷 C_AptitudeUpRequest(32)/S_AptitudeUpResponse(33) · AptitudeInfo.Cap · CharacterInfo.AptitudePoints · 결과 코드 700·701(결번).
5.3 ❌ 미정 — 이 문서에서 결정하지 않은 것
Agent는 아래를 임의로 정하지 않는다. 구현이 필요하면 사용자에게 확인한다.
| # | 미정 항목 | 무엇이 막히나 |
|---|---|---|
| 1 | 추가 경험치 획득 경로 (먹이기 등) | 지금은 판정뿐이다(5.2). 먹이기를 두면 하위 아이템의 수요처가 생긴다 → 산업 레벨 4.2 |
| 3 | 가챠 천장 · 확정 지급 경로 | 높은 적성을 얻는 길이 운뿐이다 → 5.1 (콘텐츠 해금과는 무관 → 2.3) |
레벨 효과는 5.4로 확정됐다(2026-10-06 작업속도 가산으로 재확정 · T-003).
6. 데이터 · 구현 접점
| 대상 | 내용 |
|---|---|
Character.xlsx |
CharacterTable: CharacterTID · Name · GlobalRarity · 산업 적성 5열 · BasePrice · Description (Race는 보류 → 4장) · CharacterLevelTable: 레벨(=TID) → RequiredExp · SpeedAddPermille (100행 → 5.2 · 5.4) |
Gacha.xlsx |
GachaInfoTable(풀 이름·비용) · GachaCharacterTable(캐릭터 풀) → 5.1 · GachaEquipTable(장비 풀 3개) → 5.1.1 |
WorkSpeedTable |
적성 → 기본 작업속도(천분율). Ref로 적성값을 검증한다 |
IndustryLevelTable.ExpPerJudge |
(산업, 레벨)별 판정 1회당 캐릭터 경험치 → 5.2 |
t_character |
개체 PK · TID · 레벨 · 경험치(현재 레벨에서 쌓은 양 → 5.2) |
Equip.xlsx · t_user_equip · t_character_equip |
EquipTable(종류 EquipKind · 대상 산업 · SpeedAddPermille) · 유저 소유 장비 개체(인벤토리 칸 번호 포함) · (캐릭터, 칸 EquipSlot) → 개체 매핑 → 1장 #6. 상세는 docs/superpowers/specs/2026-09-17-equipment-design.md |
EquipEnchant.xlsx · t_user_equip 인챈트 4열 |
EnchantOptionTable(등급별 옵션 풀 · 고정 Value · Weight) · EnchantGradeTable(장비 등급별 SlotCount · 인챈트 등급별 UpPermyriad) · EnchantItemTable(큐브 → UpRatePermille) · 개체의 enchant_grade · enchant_1~3(EnchantOptionTID, 0 = 빈 칸) → 1.4 |
| 패킷 | CharacterInfo.Aptitudes — AptitudeInfo{Industry, Value} 목록 → 7.1 · S_CharacterSyncResponse — 레벨·경험치가 바뀐 개체 1건 푸시 → 5.2 |
| 서버 | User/Character/Character.cs(GetAptitude·Industries) · User.Character.cs · User.WorkStation.cs(ResolveSlotSpeed — 착용 장비·특성·레벨 가산 · SettleWorkStation — 경험치 가산 · 레벨업 시 속도 재계산) · Common/CharacterLevelCatalog.cs · Repository/CharacterGrowthRepository.cs · Gacha/GachaService.cs(지급) · User.Equip.cs · Common/EquipCatalog.cs · Repository/EquipRepository.cs(장비 — 패킷 S_EquipListResponse · S_EquipSyncResponse · C_EquipRequest · C_UnequipRequest · S_EquipResponse) · User.Enchant.cs · Common/EnchantCatalog.cs(인챈트 — 패킷 C_EquipEnchantRequest · S_EquipEnchantResponse, 개체 갱신은 S_EquipSyncResponse · EquipInfo.EnchantGrade·EnchantOptions) |
밸런스 수치는 코드 상수가 아니라 엑셀에 둔다. 수정 후
GameDesign/generate-tables.ps1실행.
7. 서버 / 클라이언트 책임
| 책임 | 주체 |
|---|---|
적성 조회 · 배치 가능 판정(CanWork) |
서버 |
적성 값 산출 · 전달 (CharacterInfo.Aptitudes) |
서버 |
| 기본 작업속도 변환 · 보정 합성 | 서버 |
| 경험치 누적 · 레벨업 판정 | 서버 ✅ (2026-09-13) |
| 포인트 계산 · 찍기 검증(포인트·상한) · 보너스 저장 | 서버 (5.4) |
| 적성 상세·포인트 찍기 화면 | 클라이언트 |
| 캐릭터 목록·적성 표시, 배치 UI | 클라이언트 |
클라이언트는
CurrentWorkSpeed(결과값)만 받는다. 보정 내역을 받지 않으므로 계산할 것이 없다.
7.1 적성은 서버가 내려준다 — 클라가 테이블에서 읽지 않는다 (2026-08-10 확정)
적성 값의 소유자는 정적 테이블이 아니라 서버 런타임이다. 클라이언트는
CharacterInfo.Aptitudes로 받은 값만 쓰고, CharacterTable을 직접 조회하지 않는다.
| 무엇 | 어디 | |
|---|---|---|
| 원본(기본 적성) | TID별 고정값 | 엑셀 CharacterTable |
| 전달값(실효 적성) | 서버가 산출한 결과 | 패킷 CharacterInfo.Aptitudes |
산업과 값을 짝지어 보낸다. 순서·인덱스 규약을 두지 않는다 — 규약은 양쪽이 어긋나도 컴파일이 통과해서, 틀리면 조용히 엉뚱한 산업의 적성을 읽는다.
public partial struct AptitudeInfo
{
public EIndustryType Industry { get; set; } // 어느 산업인지 스스로 밝힌다
public byte Value { get; set; } // 0~10
}
- 1차 산업 5종이 값 0까지 포함해 전부 실린다. 0을 빼면 클라가 "다루지 못함(잠금)"과 "정보를 못 받음"을 구분할 수 없다.
EIndustryType은 1차 산업만 담는다. 아이템 분류(Misc·Special)는 타입에 아예 없어서 적성이나 배치에 섞일 수 없다.GameData.IndustryType과 1:1이며 어긋나면 테스트가 잡는다(PacketEnumTest).
✅ 서버 안쪽도 갈랐다 (2026-08-10 · T-023).
Enum.xlsx에IndustryType을 신설해 산업을 뜻하던ItemType자리를 전부 옮겼다. 이제GetAptitude(ItemType.Misc)같은 호출이 컴파일 단계에서 막힌다 — 예전엔_ => 0이 런타임에 삼켰다.ItemTable.ItemType은 원래 뜻(아이템 분류)으로 남는다. → 기획평가 2.7(R7 해소)
- 지금은 두 값이 같다. 적성을 바꾸는 주체가 없다 — 레벨(5.4)·장비(T-002)는 적성이 아니라 속도 가산으로 들어간다.
보정이 생기면
Value에 실리고, 클라는 그대로 읽으면 된다.
왜 지금 나눠 두는가 — 장비 4칸이 붙으면 적성에 보정이 올라올 여지가 생긴다. 그때 클라가 테이블을 직접 읽고 있으면 배치 UI·필터·인벤토리 탭이 한꺼번에 틀린다. 특히 적성 0을 1로 올리는 보정이 생기면 클라가 실제로는 배치 가능한 캐릭터를 숨긴다. 전달값을 서버로 넘겨 두면 그 결정을 미룰 수 있고, 어느 쪽으로 정하든 클라는 안 바뀐다.
이는
CurrentWorkSpeed와 같은 원칙이다 — 보정이 붙는 값은 서버가 결과만 내려준다.
✅ 장비는 적성을 바꾸지 않는다 (2026-09-17 확정 · T-002). 장비 효과는 속도 가산뿐이며 (작업슬롯 3.4), 적성은 그 식의 입력으로 남는다. 장비 때문에 적성 푸시가 필요해질 일은 없어졌다 — 일감 T-022의 사유가 소멸했다.