Merge branch 'wl/systems/WL-813n-skill-data'

This commit is contained in:
깃 관리자 2026-09-09 00:18:24 +09:00
commit 21c0fae954
5 changed files with 263 additions and 3 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,259 @@
# WL-813n — 스킬 4슬롯 프리셋 데이터 초안 · 적용 절차
> 산출: `Assets/ResWork/Table/Export/_WL813n_SkillList.json` (**게임 미참조 초안** · 원본 `SkillList.json` 과 스키마 100 % 동일 · 35 필드 · 16 행)
> 규격: `핵앤슬래시_기준서_v1.md` §B(요소 3 · 쿨 · 자원 행) · §C(요소 3) · §D-1 813n 행 · §F-1(4슬롯 확정)
> 코드 변경 0 · 원본 테이블 변경 0 · 유니티 에디터 미사용(실측은 python 집계 + grep)
---
## 0. 한 줄 요약
근접 4클래스(10101 onehand · 10102 spellblade · 10501 bluntshield · 10502 paladin) × 4슬롯 = **16행**.
바꾼 필드는 **`f_CoolTime` · `n_MP` 딱 2개**(슬롯별 6/8/12/20 s · 10/15/25/40). 나머지 33 필드는 원본 값 그대로.
**행 순서 = 슬롯 순서**(클래스별 4행 연속 · 배열 index 0~3 = `PCActor.arr_magicData[0..3]`) — 원본 스키마에 슬롯 열이 없으므로 순서로 표현한다.
---
## 1. 프리셋 16행 (바꾼 필드만 원본 대비)
근거 줄(기준서 v1 §B · "3 스킬 / 쿨 · 자원" 행):
> 슬롯별 쿨 **6 / 8 / 12 / 20 s** · MP 100 기준 소모 **10 / 15 / 25 / 40** · MP 자연 회복 **5 %/s** · 수동 = 즉시 · 자동 = 2~3 s 간격 유지
> (현행 실측: Active 52 중 50 이 쿨 **5 s 균일** · `n_MP` 전부 5 · 자동 스킬 간격 `Random.Range(2,3)`)
| 클래스 | 슬롯 | SkillID | e_Skill | 이름 | n_SkillClassID | f_CoolTime | n_MP | f_SkillRange | f_CastingTime | s_Animation | e_SkillTarget | e_SkillExtraType |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 10101 | 1 | 703101 | Skill_OneHandCombo1 | 연속 베기 | 10101 | 5 → **6** | 5 → **10** | 2 | 0 | combo01 | Enemy | None |
| 10101 | 2 | 703102 | Skill_FatalBlow | 회심의 일격 | 10101 | 5 → **8** | 5 → **15** | 5 | 0 | fatalblow | Enemy | None |
| 10101 | 3 | 703103 | Skill_DashSlash | 돌진 베기 | 10101 | 5 → **12** | 5 → **25** | 7 | 0 | dashslash | Enemy | None |
| 10101 | 4 | 702006 | Skill_Tornado | 돌개 바람 | 0 | 5 → **20** | 5 → **40** | 10 | 1 | casting | Enemy | Airborne |
| 10102 | 1 | 703106 | Skill_BrutalSlash | 무자비한 일격 | 10102 | 5 → **6** | 5 → **10** | 5 | 0 | brutalslash | Enemy | None |
| 10102 | 2 | 703105 | Skill_EnergySlash | 검기 분출 | 10102 | 5 → **8** | 5 → **15** | 8 | 0 | energyslash | Enemy | None |
| 10102 | 3 | 703104 | Skill_SwordShield | 칼날 방패 | 10102 | 5 → **12** | 5 → **25** | 10 | 0 | skill1 | Self | SwordShield |
| 10102 | 4 | 702009 | Skill_ExplosionArea | 마나 폭발 | 0 | 5 → **20** | 5 → **40** | 10 | 1 | casting | Self | None |
| 10501 | 1 | 703502 | Skill_ShieldAttack | 방패 치기 | 10501 | 5 → **6** | 5 → **10** | 2 | 0 | shieldattack | Enemy | Stun |
| 10501 | 2 | 703503 | Skill_JumpAttack | 강력한 일격 | 10501 | 5 → **8** | 5 → **15** | 10 | 0 | jump | Enemy | Stun |
| 10501 | 3 | 703501 | Skill_EatSnack | 간식 먹기 | 10501 | 5 → **12** | 5 → **25** | 0 | 0 | eat | Self | None |
| 10501 | 4 | 702005 | Skill_StraightBurst | 휘몰아치는 파도 | 0 | 5 → **20** | 5 → **40** | 10 | 1 | casting2 | Enemy | None |
| 10502 | 1 | 703505 | Skill_CrossSlash | 십자 가르기 | 10502 | 5 → **6** | 5 → **10** | 2 | 0 | crossslash | Enemy | None |
| 10502 | 2 | 703506 | Skill_SpinSlash | 회전 베기 | 10502 | 5 → **8** | 5 → **15** | 12 | 0 | spinslash | Enemy | None |
| 10502 | 3 | 703504 | Skill_Aggro | 도발 | 10502 | 5 → **12** | 5 → **25** | 0 | 0 | aggro | SelfEnemy | Aggro |
| 10502 | 4 | 702012 | Skill_HeavyRain | 심판의 빛 | 0 | 5 → **20** | 5 → **40** | 10 | 1 | casting | Enemy | None |
### 1-1. 선택 규칙 (근거)
- **슬롯 1~3 = 클래스 일치 행**: 근접 4클래스는 `ClassConfig.n_AcquiredSkill1~3` 에 각각 Active 3행만 갖는다(실측 · `table_skilllist.cs:257` 의 클래스 스킬 목록도 JobPassive 를 제외해 3행). 그래서 슬롯 1~3 은 이 3행이 전부이고, **선택압은 슬롯 4에만 걸린다**.
- **슬롯 순서 = 1 최단 사거리·상시 → 2 중거리/돌진 → 3 유틸·버프 → 4 최대 광역(캐스팅 1 s)**. `f_SkillRange` 실측으로 정렬.
- **슬롯 4 = 광역 우선**: `n_SkillClassID = 0` 인 Active 16행(702001~702016) 풀에서 **광역 4종**을 클래스 원소에 맞춰 배분.
- `Actor.cs:700` 의 클래스 게이트가 `n_SkillClassID > 0 && != m_ID` 일 때만 차단하므로 **`0` 행은 4클래스 모두 통과**한다(실측 근거).
- 애니 상태 실측: `pcanim_onehand / spellblade / bluntshield / paladin` **4개 컨트롤러 모두 `casting` · `casting2` 상태 보유** → 702xxx 행의 `s_Animation` 재생 가능.
- 배분 근거 — 10101 돌개 바람(회오리 · Airborne CC) · 10102 마나 폭발(`e_SkillTarget=Self` 자기중심 폭발 · 마검사) · 10501 휘몰아치는 파도(둔기 충격파) · 10502 심판의 빛(성기사 낙하 광역).
- `n_SkillClassID`**0 그대로 둔다**(바꾼 필드 최소화 · 다른 클래스 재사용 여지 유지). 대신 아래 §5 ①의 부작용을 참고할 것.
- **자동/수동 규칙 = 코드 0 으로 이미 충족**: `PCActor.Update` 자동 분기만 `t_autoskill <= 0f` 를 조건에 걸고 발동 후 `t_autoskill = Random.Range(2f, 3f)` 로 재장전한다(= 자동 2~3 s 유지). 수동 경로에는 이 게이트가 없어 **수동 = 즉시**. 데이터로 바꿀 것이 없다.
### 1-2. ⚠ 적용 전 반드시 같이 바꿔야 하는 선행 값 (이 초안 범위 밖 · 원본 파일이라 손대지 않음)
`Actor.cs:733-741``needMP > 현재 MP` 면 스킬이 **불발**(`return false`)하는 게이트가 있다. 그런데 실측 MP 풀은 **100 이 아니라 30**:
| 값 | 현행(실측) | 기준서 §B 전제 | 조치 |
|---|---|---|---|
| `GlobalValue.MP` | **30** | MP **100** 기준 | 100 으로 올리지 않으면 **슬롯 4(40 MP)는 영구 불발** · 슬롯 3(25)도 만탱일 때만 발동 |
| `ClassConfig.n_DefaultMP` (12행 전부) | **30** | 100 | 위와 동일 |
| `GlobalValue.MP_REGEN` | **3** | 5 %/s = 100 기준 초당 **5** | 5 로 상향 |
**이 3개를 함께 반영하지 않으면 프리셋은 리듬을 만들지 못한다.** 813c(패드) 착수 전 Lead 결정 필요.
---
## 2. 811i 이펙트 매핑 입력 열 (차지 / 본체 / 착탄)
- 후보는 전부 `Assets/Res_Addr/Effect/**` **실존 프리팹명**(하위 폴더 포함 303개 중 선택 · 실존 grep **불일치 0건**).
- 클래스 원소는 각 행의 `e_SkillExtraType``SkillTypeConfig.e_SkillEffect` 로 확인(상태 이펙트는 이미 테이블이 재생하므로 아래 표에서 제외).
- 811i 설계안 §B-3 `vfxSlots` = `{phase Charge/Body/Impact, prefab, anchor, scale}` 의 **prefab 열 입력값**으로 그대로 쓰면 된다.
| 클래스 | 슬롯 | SkillID | charge | body | impact | 상태 이펙트(SkillTypeConfig 자동) |
|---|---|---|---|---|---|---|
| 10101 | 1 | 703101 | — | Effect_Slash_10101_1 | Effect_Hit_10101 | — |
| 10101 | 2 | 703102 | Effect_Berserk | Effect_Slash_10101_2 | Effect_Hit_10101 | — |
| 10101 | 3 | 703103 | Effect_PCDash | Effect_Slash_10101_3 | Effect_Hit_SweepAttack | — |
| 10101 | 4 | 702006 | Effect_Howling | Effect_WLSwingArc | WindBullet_Hit | Airborne(e_SkillEffect = None) |
| 10102 | 1 | 703106 | — | Effect_Slash_10102_1 | Effect_Hit_10102 | — |
| 10102 | 2 | 703105 | Effect_Casting_Energyball | Effect_Slash_10102_2 | Effect_Hit_Skill_EnergySlashFlash | — |
| 10102 | 3 | 703104 | Effect_Casting_ManaShield | Effect_SwordShield | — | SwordShield → Effect_SwordShield |
| 10102 | 4 | 702009 | Effect_Casting_Meteor | Effect_STR_EXTRA_SPLASH | Effect_Hit_Energyball | — |
| 10501 | 1 | 703502 | — | Effect_Slash_10501_1 | Effect_Hit_10501 | Stun → Effect_Stun |
| 10501 | 2 | 703503 | Effect_PCDash | Effect_Slash_10501_2 | Effect_Hit_SweepAttack | Stun → Effect_Stun |
| 10501 | 3 | 703501 | — | Effect_Heal | — | — |
| 10501 | 4 | 702005 | Effect_Howling | Effect_Slash_10501_3 | WaveShotHit | — |
| 10502 | 1 | 703505 | — | Effect_Slash_10502_1 | Effect_Hit_Skill_CrossSlash | — |
| 10502 | 2 | 703506 | — | Effect_Slash_10502_2 | Effect_Hit_Skill_SpinSlash | — |
| 10502 | 3 | 703504 | Effect_Howling | Effect_Aggro | — | Aggro → Effect_Aggro |
| 10502 | 4 | 702012 | Effect_Casting_Meteor | ShineBulletFlash | ShineBulletHit | — |
- 슬롯 4는 `f_CastingTime = 1` 이므로 **charge 페이즈가 실제로 1 s 존재**한다 → 811i 의 차지 연출이 붙을 자리가 슬롯 4에만 있다(슬롯 1~3 은 `f_CastingTime = 0` · charge 는 발동 순간 플래시로 처리).
- 「미확인」: 프리팹의 **재생 길이 · 파티클 수 · 스케일**은 에디터를 열지 않아 실측 못 함. 기준서 §B "대형 이펙트 동시 2 · fps 저하 ≤3" 판정은 811i/811b 의 `EffectBudget` 에서 해야 한다.
---
## 3. 고아 6행 정리 제안
인계서 §3-0 이월 항목 "butler.xlsm 동기화(+ **SkillList 고아 6행** 정리)" 의 그 6행을 실측으로 특정했다.
정의 = **`n_SkillClassID``ClassConfig` 12행에 없는 클래스 ID 를 가리키는 행**(0 은 제외). 정확히 6행.
| SkillID | n_SkillClassID | 존재? | e_SkillType | 이름 | 실제 참조처(실측) | 사유 | 처리안 |
|---|---|---|---|---|---|---|---|
| 700004 | 10203 | 없음 | JobPassive | 마나 절약 | `ClassConfig(10202 assassin).n_ExtraSkill` | 열 값만 미출시 클래스 ID | **보류(삭제 금지)** · `n_SkillClassID` 를 10202 로 정정 |
| 700008 | 10304 | 없음 | JobPassive | 약점 공격 | `ClassConfig(10401 rookiearcher).n_ExtraSkill` | 〃 | **보류** · 10401 로 정정 |
| 700009 | 10305 | 없음 | JobPassive | 현상금 사냥꾼 | `ClassConfig(10402 archer).n_ExtraSkill` | 〃 | **보류** · 10402 로 정정 |
| 700010 | 10306 | 없음 | JobPassive | 방어구 침투 | `ClassConfig(10501 bluntshield).n_ExtraSkill` | 〃 | **보류** · 10501 로 정정 |
| 700011 | 10307 | 없음 | JobPassive | 마법의 대가 | `ClassConfig(10502 paladin).n_ExtraSkill` | 〃 | **보류** · 10502 로 정정 |
| 700012 | 10308 | 없음 | JobPassive | 행복한 유대 | `ClassConfig(10601 twohand).n_ExtraSkill` | 〃 | **보류** · 10601 로 정정 |
**세션 3 로그의 "런타임 도달 불가 → 조치 불필요" 판단을 정정한다.**
근거: `MyValue.Set_JobPassive`(`MyValue.cs:497`)가 `job.n_ExtraSkill` 로 이 6행을 **매 클래스 세팅마다 실제로 읽어** `e_SkillEnhance` · `f_ExtraValue*` 를 스탯에 반영한다. 즉 **행 자체는 살아 있는 경로**이고, 도달 불가인 것은 `n_SkillClassID` **열 값 하나뿐**이다.
- 삭제하면 6개 클래스의 직업 패시브 스탯이 통째로 사라진다 → **삭제 불가**.
- `n_SkillClassID` 는 JobPassive 조회 경로에서 안 읽히므로(`table_skilllist.cs:257` 이 JobPassive 를 제외 · `Actor.cs:700` 게이트는 `Use_Skill` 전용) **지금 당장의 버그는 아니다**. 우선순위 = 낮음, 위험 = 낮음, 조치 = **xlsm 열 값 정정만**.
- 참고(별건): 같은 열 오류가 살아 있는 클래스에도 1건 있다 — **`700002`(마검사 직업 패시브)의 `n_SkillClassID``10201`(bladedancer)** 인데 실제 참조처는 `ClassConfig(10102 spellblade).n_ExtraSkill` 이다. 정정 대상에 함께 올릴 것을 권고.
---
## 4. 적용 절차 (butler.xlsm SOT → Export)
> ⚠ **xlsm 동기화 필요.** `Assets/ResWork/Table/Export/*.json`**산출물**이고 SOT 는 `Assets/ResWork/Table/butler.xlsm` 이다. Export JSON 만 고치면 다음 표 추출 때 **되돌아간다**.
1. **결정**: §1-2 의 MP 풀(30 → 100) · MP_REGEN(3 → 5) 을 Lead/PD 가 확정한다. 미확정이면 여기서 멈춘다(슬롯 3·4 가 불발이라 검증이 무의미).
2. **xlsm 편집**: `butler.xlsm``SkillList` 시트에서 위 표 16행의 **`f_CoolTime` · `n_MP` 두 열만** 수정. 다른 열·다른 행은 건드리지 않는다.
- 같은 파일의 `GlobalValue` 시트에서 `MP` · `MP_REGEN`, `ClassConfig` 시트에서 `n_DefaultMP` 를 1번 결정대로 수정.
- (선택) §3 의 `n_SkillClassID` 7건(고아 6 + 700002) 정정.
3. **Export 재생성**: 기존 표 추출 경로로 `SkillList.json` · `GlobalValue.json` · `ClassConfig.json` 재출력. 인코딩은 **UTF-8 BOM · 단일 라인 · 전 값 문자열** 을 유지할 것(이 초안 파일이 같은 규격이니 diff 로 대조하면 된다).
4. **서버 열 확인 「미확인」**: `Assets/Editor/GetServerTables.cs:19` 의 SkillList 서버 열 목록에 **`n_MP` · `f_CoolTime` 이 포함**되어 있다 → 이 두 값은 서버 테이블로도 나간다. 클라 단독 반영이 서버 값에 덮이는지는 서버 쪽을 못 봐서 확인 못 했다. **반영 전 서버 담당 확인 필요.**
5. **슬롯 장착**: 실제 4슬롯은 `ServerClass.Get_EquipSkillID(slot)` = `Equip.Skill[Preset][slot]` 에서 온다(`ServerClass.cs:456`). 이 초안은 **어떤 스킬이 어느 슬롯에 와야 하는지**를 정의할 뿐이고, **장착 자체는 서버 프리셋(또는 신규 클래스 기본 장착 데이터)** 이 해야 한다 → 813c 착수 시 같이 처리.
6. **초안 파일 정리**: 본 표 반영이 끝나면 `_WL813n_SkillList.json` 은 역할이 끝난다(게임 미참조). 삭제 여부는 Lead 판단.
---
## 5. 검증 방법 (813c · PCActor 쿨 경로)
**① 813c 패드가 읽는 슬롯 필드** (`PCActor.cs:151-159`)
```
arr_PCSkillCard = NewGameUI.Ins.m_BattleUI.skillCards; // 6칸 배열 (프리팹 축소 아님 · F-1 대로 4칸만 채움)
skillData = table_skilllist.Ins.Get_Data_orNull(sdata.Get_EquipSkillID(i));
arr_PCSkillCard[i].Set(skillData); arr_PCSkillCard[i].Set_CardType(eUICardType.Use);
```
→ 813c 는 index **0~3 만 활성**, 4·5 는 비활성(물약/회피 자리)으로 두면 §F-1 "프리팹 배열을 줄이지 않고 4만 활성" 이 그대로 성립.
카드 라벨의 MP 표시는 `UIItemCard.cs:280,284``n_MP` 를 그대로 찍는다 → 반영 즉시 10/15/25/40 이 보이면 성공.
**② 쿨 감소 경로** (`PCActor.Update` · `PCActor.cs:78~103`)
```
arr_magicCoolTime[i] -= Time.deltaTime; // :85 매 프레임 감소
arr_PCSkillCard[i].Set_CoolTime(arr_magicCoolTime[i], arr_magicMaxCoolTime[i], i); // :91 쿨 링 소스
... arr_magicCoolTime[i] = arr_magicMaxCoolTime[i]; t_autoskill = Random.Range(2f,3f); // :97-98
```
- `arr_magicMaxCoolTime[i]``PCActor.cs:190-191` 에서 `f_CoolTime``COOLTIME_Multiplier` 를 적용해 세팅(`table_skilllist.cs:211` `Get_CoolTime`). `GlobalValue.COOLTIME_Multiplier` 실측 = **0** → 감쇄 없음 → **표 값이 그대로 쿨**이다.
- **판정 기준**: 4슬롯 쿨 링이 각각 6 / 8 / 12 / 20 s 주기로 차오르고, 자동 발동 간격이 2~3 s 로 유지되면 성공. 슬롯 4가 20 s 마다 한 번만 터지는지 로그로 확인(813m `SkillCast` 카운트).
- **주의**: 자동 발동에는 `Vector3.Distance(...) <= f_SkillRange` 조건이 붙는다(`PCActor.cs:93`). 슬롯 4의 `f_SkillRange = 10` 은 근접 클래스 교전 거리(`ClassConfig.f_AttackRange` 1.5)보다 훨씬 넓어 사거리로는 막히지 않는다.
**③ 회귀 체크**: 원본 `SkillList.json`**바이트 무변경**(123,125 bytes) — 이 초안은 게임이 읽지 않는 새 파일이므로 반영 전까지 런타임 영향 0.
---
## 6. 롤백
> 🔻 **§8(2단계 적용) 이후 상태가 현행이다.** 아래 "반영 전" 항목은 1단계 시점 기록으로 남겨 둔다.
- **반영 전(1단계 시점)**: 새 파일 2개(`_WL813n_SkillList.json` · 본 문서)만 존재. `git -C E:\NerdNavis\WL_wt\data revert <커밋>` 또는 두 파일 삭제로 끝. 게임 영향 0.
- **반영 후(xlsm 편집 뒤)**: 되돌릴 값은 **`f_CoolTime = 5` · `n_MP = 5`**(16행 전부 동일하므로 일괄 복구 가능) + `GlobalValue.MP = 30` · `MP_REGEN = 3` · `ClassConfig.n_DefaultMP = 30`(12행). xlsm 은 git 이력에서 이전 리비전을 꺼내고 Export 를 재생성하는 것이 가장 안전하다.
- 813c 패드가 이미 붙은 뒤라면 데이터만 되돌려도 패드는 그대로 동작한다(패드는 슬롯 값을 읽기만 한다).
---
## 7. 미확인 (에디터·서버 없이 확인 불가)
1. 서버가 `n_MP` · `f_CoolTime` 을 덮어쓰는지(`GetServerTables.cs:19` 서버 열 목록에 포함) — **서버 담당 확인 필요**.
2. 슬롯 4 후보(702005/702006/702009/702012)의 **실제 타격 범위·명중 수**. 기준서 §B "광역 반경 2.5~4 m · 1발 ≥3마리" 충족 여부는 Play 로만 판정 가능(`Skill_*` 프리팹은 `Res_Addr/Skill/`·`Res_Addr/Projectile/` 에 실존 확인까지만 함).
3. 이펙트 프리팹의 재생 길이·파티클 수·동시 상한(fps 저하 ≤3) — 811i/811b 영역.
4. `Equip.Skill[Preset][slot]` 기본 장착값이 어디서 초기화되는지(서버 데이터) — 813c 와 함께 확인 필요.
5. `casting` / `casting2` 상태의 **전이 조건**(`skill4` 등 다른 상태와의 우선순위) — 컨트롤러의 상태 존재만 grep 으로 확인했고 전이 그래프는 확인 못 함.
---
## 8. 2단계 적용 내역 (실테이블 반영 · Lead 결정 #813 일괄 위임)
> ⚠ **xlsm 동기화 필요.** 아래 45셀은 **Export JSON 에 직접** 넣은 값이다. SOT 는 `Assets/ResWork/Table/butler.xlsm` 이므로,
> xlsm 의 `SkillList` · `GlobalValue` · `ClassConfig` 시트에 같은 값을 넣지 않으면 **다음 표 추출 때 전부 되돌아간다.**
> §4 의 적용 절차 2번(xlsm 편집)이 아직 남아 있는 상태다.
### 8-1. 변경 파일 3개 (전수)
| 파일 | HEAD bytes → 현행 | 행 수 | 필드 수 | 키 순서 | BOM | 변경 셀 |
|---|---|---|---|---|---|---|
| `Assets/ResWork/Table/Export/SkillList.json` | 123,125 → **123,149** | 130 (불변) | 35 (불변) | 불변 | 유지 | **39** |
| `Assets/ResWork/Table/Export/GlobalValue.json` | 5,053 → **5,054** | 75 (불변) | 3 (불변) | 불변 | 유지 | **2** |
| `Assets/ResWork/Table/Export/ClassConfig.json` | 7,743 → **7,747** | 12 (불변) | 24 (불변) | 불변 | 유지 | **4** |
- 바이트 증가분(+24 / +1 / +4)은 **전부 값 자릿수 증가분**과 정확히 일치(구조 변경 0).
- 행 추가·삭제 **0** · 필드 추가·삭제 **0** · 전 값 `str` 타입 유지 · 단일 라인(개행 0) 유지.
- 코드 변경 **0** · 프리팹/씬/에셋 변경 **0** · 유니티 에디터 미사용.
### 8-2. diff 표 (구값 → 신값 · 45셀)
**① `SkillList.json` 프리셋 16행 × 2필드 = 32셀** (§1 표대로 · 다른 33필드 무변경)
| SkillID | 클래스/슬롯 | `f_CoolTime` | `n_MP` |
|---|---|---|---|
| 703101 / 703102 / 703103 / 702006 | 10101 · 1~4 | 5→**6** / 5→**8** / 5→**12** / 5→**20** | 5→**10** / 5→**15** / 5→**25** / 5→**40** |
| 703106 / 703105 / 703104 / 702009 | 10102 · 1~4 | 5→**6** / 5→**8** / 5→**12** / 5→**20** | 5→**10** / 5→**15** / 5→**25** / 5→**40** |
| 703502 / 703503 / 703501 / 702005 | 10501 · 1~4 | 5→**6** / 5→**8** / 5→**12** / 5→**20** | 5→**10** / 5→**15** / 5→**25** / 5→**40** |
| 703505 / 703506 / 703504 / 702012 | 10502 · 1~4 | 5→**6** / 5→**8** / 5→**12** / 5→**20** | 5→**10** / 5→**15** / 5→**25** / 5→**40** |
**② `SkillList.json` 고아/오류 열 정정 7건 = 7셀** (`n_SkillClassID` 만 · **행 삭제 0**)
| SkillID | 이름 | 구값 | 신값 | 근거(실측) |
|---|---|---|---|---|
| 700002 | 약자 멸시 | 10201 | **10102** | `ClassConfig(10102).n_ExtraSkill = 700002` |
| 700004 | 마나 절약 | 10203 | **10202** | `ClassConfig(10202).n_ExtraSkill = 700004` |
| 700008 | 약점 공격 | 10304 | **10401** | `ClassConfig(10401).n_ExtraSkill = 700008` |
| 700009 | 현상금 사냥꾼 | 10305 | **10402** | `ClassConfig(10402).n_ExtraSkill = 700009` |
| 700010 | 방어구 침투 | 10306 | **10501** | `ClassConfig(10501).n_ExtraSkill = 700010` |
| 700011 | 마법의 대가 | 10307 | **10502** | `ClassConfig(10502).n_ExtraSkill = 700011` |
| 700012 | 행복한 유대 | 10308 | **10601** | `ClassConfig(10601).n_ExtraSkill = 700012` |
→ 정정 근거는 **문서 인용이 아니라 `ClassConfig.n_ExtraSkill` 12행 역참조 실측**으로 재확인했다(7건 전부 일치).
**③ `GlobalValue.json` 2셀 · `ClassConfig.json` 4셀 = 6셀** (§1-2 선행 블로커 해소)
| 파일 | 행(키) | 필드 | 구값 → 신값 |
|---|---|---|---|
| GlobalValue | `MP` | `n_Value` | `"30"`**`"100"`** |
| GlobalValue | `MP_REGEN` | `n_Value` | `"3"`**`"5"`** |
| ClassConfig | 10101 / 10102 / 10501 / 10502 | `n_DefaultMP` | `"30"`**`"100"`** (4행) |
- `ClassConfig`**근접 4클래스만** 상향했다. 나머지 8행(10201·10202·10301~10303·10401·10402·10601)은 **`"30"` 그대로** — 이 4클래스만 813n 프리셋 대상이라 다른 클래스의 밸런스를 건드리지 않기 위함. **비근접 8클래스는 여전히 §1-2 의 MP 불일치 상태**이며, 그 클래스로 4슬롯을 확장할 때 함께 올려야 한다.
- 이로써 슬롯 4(40 MP)·슬롯 3(25 MP)가 `Actor.cs:733-741` MP 게이트를 통과한다(풀 100 · 초당 5 회복 = 슬롯 4 재충전 8 s < 20 s).
### 8-3. 검증 (python `utf-8-sig` · git)
| 항목 | 결과 | 방법 |
|---|---|---|
| 초안 델타 = `{f_CoolTime, n_MP}` 뿐 | **True** (16행 전수 · 델타 필드 합집합 2개) | 초안 vs 원본 35필드 전 셀 비교 |
| 재직렬화 왕복 무손실 | **3파일 전부 byte-identical** | `json.dumps(ensure_ascii=False, separators=(', ', ': '))` == 원문 (변경 전 사전 확인) |
| 행 수 · 필드 수 · 키 순서 · 전 값 str · BOM | **전부 불변** | `[list(r.keys()) for r in data]` 리스트 동일성 + `isinstance(v, str)` 전수 |
| 변경 셀 수 | **39 / 2 / 4 = 45** (의도치와 정확히 일치) | `git show HEAD:<path>` vs 현행 파일 셀 단위 비교 |
| 변경 필드 종류 | SkillList 3종(`f_CoolTime`·`n_MP`·`n_SkillClassID`) · 그 외 32필드 무변경 | 동일 스크립트 |
| 변경 파일 범위 | `git status --porcelain` = **M 3건만** | 허용 3파일 + 본 문서 외 수정 0 |
| 컴파일 게이트 | **미실행(해당 없음)** | 코드 0 · `unity` 명령 0회 |
`git diff --stat` = `3 files changed, 3 insertions(+), 3 deletions(-)` (단일 라인 JSON 이라 파일당 1줄로 계상).
### 8-4. 롤백
- **가장 안전**: `git -C E:\NerdNavis\WL_wt\data revert <2단계 커밋>` — 3파일이 1단계 상태(`f_CoolTime="5"` · `n_MP="5"` · `MP="30"` · `MP_REGEN="3"` · `n_DefaultMP="30"`)로 정확히 복원된다. 1단계 산출물(초안 JSON · 본 문서)은 그대로 남는다.
- **부분 롤백**: 밸런스만 되돌리려면 `git checkout <1단계 커밋> -- Assets/ResWork/Table/Export/SkillList.json`(프리셋+고아정정 동시 원복) 또는 파일별로 선택.
- **xlsm 을 이미 편집한 뒤라면** Export 롤백만으로는 부족하다 — xlsm 도 git 이력의 이전 리비전으로 되돌리고 Export 를 재생성해야 한다(안 그러면 다음 추출 때 되살아난다).
- 813c 패드가 붙은 뒤에도 **데이터만 되돌리면 된다**(패드는 슬롯 값을 읽기만 함 · §5-①).
### 8-5. 남은 선행 조건 (2단계로 해소되지 않음)
1. **xlsm 동기화** — 위 45셀을 `butler.xlsm` 에 반영(§4-2). 미반영 시 다음 Export 에서 소실.
2. **서버 덮어쓰기 「미확인」**`GetServerTables.cs:19` 서버 열 목록에 `n_MP`·`f_CoolTime` 포함. 서버가 이 두 값을 내려주면 클라 테이블 값이 무시될 수 있다(§7-1 · 서버 담당 확인 필요).
3. **슬롯 장착**`Equip.Skill[Preset][slot]`(`ServerClass.cs:456`)은 여전히 서버 프리셋 소관. 본 적용은 "어느 스킬이 어떤 쿨/코스트를 갖는가"만 정했고 **장착 자체는 813c 와 함께** 처리해야 한다.
4. **Play 검증 미실시** — 쿨 링 6/8/12/20 s · MP 바 10/15/25/40 소모 · 슬롯 4 광역 명중 수는 에디터 없이 확인 불가(§5-② 판정 기준 참조).