ThreeKingdomsSlingshot/CLAUDE.md

120 lines
46 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 삼국지 슬링샷 (가제) — 에이전트 작업 지침
> 너드나비스 조직 프로젝트 ⑤ (조직 레포 `.claude/skills/너드나비스-코어룰/rules/P38.md` §P38-3 ⑤). 새 세션은 조직 레포 `공유/인계서/삼국지슬링샷_인계서.md` → 이 파일 → `README.md` 순서로만 읽고 시작한다. 조직 규칙 본문 전수 읽기 금지(C10-1).
## 프로젝트 정보
- Unity **6000.3.23f1** · 2D · 빌트인 RP · 레거시 Input(`activeInputHandler: 0`) · Portrait · 패키지 = 기본 모듈 + `com.unity.ugui 2.0.0` + `com.unity.test-framework 1.6.0` + `com.unity.pipeline 0.10.0-exp.1`(2026-11-04 P38-7 적용).
- git = 이 폴더가 레포 루트 · `main` · 원격 `origin` = `https://burning.i234.me/NerdNavis_AiDev/ThreeKingdomsSlingshot.git`(비공개 · 2026-11-09 · 다른 PC 와 함께 작업 = 시작 전 `git pull` · 커밋 후 `git push`). 미추적 = `Library/` `Temp/` `Logs/` `UserSettings/` `obj/`(Unity 표준 `.gitignore` · Pipeline 포트 파일 `Library/Pipeline/.unity-pipeline-port` 도 그 안).
- 기록 = 조직 레포 `공유/대화로그/삼국지슬링샷/` · 인계서 `공유/인계서/삼국지슬링샷_인계서.md` · PD 지시 로그 #1077.
## 에디터 제어 = Unity 공식 CLI + `com.unity.pipeline` (P38-7)
기본 호출은 MCP 가 아니라 **셸에서 `unity …` 직접 호출**이다(Unity 공식 권장 · 토큰 적음). `unity` 명령은 PowerShell 로 호출하되, C# 코드에 큰따옴표가 들어가는 `eval` 류는 Bash 도구로(K-DEV-026). 에디터가 둘 이상 열려 있으면 `--project-path E:\Nerdnavis\ThreeKingdomsSlingshot` 또는 환경 변수 `UNITY_PROJECT_PATH` 로 대상을 지정한다(K-DEV-024).
### 에디터가 열려 있을 때 (PD 플레이 중 반복 수정)
```
unity status --project-path . # ready 확인(포트·PID)
unity recompile --project-path . # 수정 후 재컴파일 · 컴파일 오류 0 이어야 통과
unity command --project-path . # 등록된 명령 목록(콘솔 읽기 · 메뉴 실행 등)
unity command --project-path . <명령> [인자] # 명령 실행 · --result-only 로 JSON 만
unity command --project-path . test run --mode playmode --async-tests --timeout 600 --result-only # 열린 에디터 안에서 테스트 시작(비동기 필수 · Play 진입의 도메인 리로드가 동기 요청을 끊음)
unity command --project-path . test status --result-only # 끝날 때까지 10초마다 폴링 · test list / cancel 도 있음
# ※ `unity test <proj> --mode PlayMode` 는 자체 배치 인스턴스를 띄우므로 에디터가 열려 있으면 거부된다(2026-11-04 실측 「이미 열려 있습니다」)
unity close . [--force] # 에디터 종료(저장 없이 종료 · PD 가 연 에디터는 닫지 않는다)
```
- **작업 전에 `unity command --project-path . editor status --result-only` 로 `playMode` 를 본다.** **PD 가 Play 중(`playing`·`paused`)이어도 기다리지 말고 `unity command --project-path . editor.playmode stop` 으로 Play 를 중단 → `stopped` 확인 → 바로 작업**한다(PD 지시 2026-11-04 · 2026-11-09 재확인 「내 PC 가 플레이 중이더라도 중단하고 작업해」 · 에디터 자체는 닫지 않음). 수정을 **모두** 끝낸 뒤 리컴파일 1회(편집 중 자동 새로고침으로 잠깐 컴파일 오류가 보일 수 있음). 열린 에디터가 있으면 배치 실행은 `Temp/UnityLockfile` 로 막히고, PD 가 연 에디터는 닫지 않는다 → 검증이 급하면 `Assets`·`Packages`·`ProjectSettings` 만 복사한 사본 폴더(예 `E:\Nerdnavis\_tks_verify`)에서 배치 실행(2026-11-04 실측 · 새 Library 임포트 포함 수 분).
- 수치 자산에 새 필드를 넣었으면(예 `art` · `archerRangeRatio`) 기존 `BalanceConfig.asset` 에는 비어 있다 → 자산이 git 에서 미수정이면 메뉴 `TKS/Reset Balance Defaults`(= `BuildFresh`)로 재시드 · PD 가 Inspector 로 손본 흔적(git diff)이 있으면 재시드 금지 · 필드만 보충.
- 판정 = 컴파일 오류 0 + 콘솔 error 0(Unity 자체 `SearchDatabase` 인덱싱 예외는 무해).
### 에디터가 닫혀 있을 때 (배치 · 재현 가능한 실측)
```
Unity.exe -batchmode -nographics -projectPath <proj> -executeMethod TKS.EditorTools.ProjectSetup.Build -quit -logFile <log> # 컴파일 + 설정 자산·씬 생성(BuildFresh = 기본값 재시드)
Unity.exe -batchmode -nographics -projectPath <proj> -runTests -testPlatform PlayMode -testResults <xml> -logFile <log> # 플레이모드 테스트(현재 17종)
Unity.exe -projectPath <proj> -executeMethod TKS.EditorTools.ProjectSetup.AutoPlayCapture -autoplayDir <dir> -autoplayTurns 3 # GUI 자동 플레이 캡처 · 스스로 종료
```
- `Unity.exe` = `C:\Program Files\Unity\Hub\Editor\6000.3.23f1\Editor\Unity.exe`. 에디터가 같은 프로젝트를 열고 있으면 배치 실행은 못 한다(`Temp/UnityLockfile`) — 먼저 `unity status` 로 확인.
- 배치 로그 판정 = `error CS` 0 · 테스트 XML `failed="0"`.
- **에디터가 열리면서 크래시하면 oCam(화면 녹화)을 의심**: `%LOCALAPPDATA%\Unity\Editor\Editor.log` 꼬리의 스택에 `HookSurfaceDLL` → `D3D12SwapChain::Present` 가 있으면 oCam 의 D3D12 훅이 원인(2026-11-04 15:04 × 2 실측). 이 프로젝트는 그래서 Windows Graphics API 를 **D3D11 우선**으로 고정했다(`ProjectSettings.asset` `m_BuildTargetGraphicsAPIs` · 에디터도 이 설정을 따름). 그래도 크래시하면 `-force-d3d11` 인수 또는 oCam 종료. 배치 `-nographics` 는 영향 없음.
## 코드 규칙
- 전투 수치 SOT = `Assets/Resources/BalanceConfig.asset`(Inspector 조정 · 최초 시드만 `Assets/Scripts/Editor/BalanceDefaults.cs`). 코드·테스트에 수치를 직접 적지 않는다(C45).
- **아웃게임 SOT = `Assets/Data/OutGame/*.csv`(12장 · 기획 `공유/기획팀_산출물/삼국지슬링샷/tables/` 사본 · utf-8-sig · 2026-11-05 #1093 A)** — 영웅·병사(`heroes.csv` · 구 `BalanceConfig.heroes[]` 대체) · 레벨·승급·진화·장비·뽑기·보상·재화·신규 유저 초기값(`economy.csv` start_*). 고친 뒤 메뉴 `TKS/Import OutGame Data (아웃게임 데이터 임포트)`(`Editor/DataImporter` · 오류 시 자산 불변) → `Resources/OutGame/` 9 자산. 표를 바꾸면 기획 `tables/` 도 같이.
- **스킬(2026-11-10 #1112 전면 개편 · PD 엑셀 「장수 목록」 새 기획 144개)** — 수치 SOT = **`Assets/Data/OutGame/skill_effects.csv`(효과 1줄 = `skill_id · key · target · cond · dur · p1~p3` + 한글 주석 열 `effect_ko · p1_ko~p3_ko` = 밸런싱용 · 게임은 안 읽음)** · `skills.csv` = 이름·발동(next_action|instant|always)·쿨·설명 · `effect_key` = 효과 키 목록(p 칸 비움) · `heroes.csv gender`(m|f = 남성·여성 조건). 원본 = 기획 `tables/`(생성기 `_tables_t1.py` SKILL_EFFECTS · KEY_DOC 주석 사전 · 이름·설명 = `영웅_리스트_스킬.md` · 실행 뒤 `economy.csv`·`gacha.csv` 는 git 되돌림 = 생성기보다 손으로 늘린 판이 최신). 처리기 = `Runtime/Skills/`: `SkillKeys`(121종 · 레벨 계수 칸) · `SkillMods`(대상 칸 자신/allies/camp/radius/range/aura · 조건 칸 crit/vs_*/turn_le/armed · 공격·피해·치명타·반격·슬링 질의) · `SkillSystem`(쿨·예약 · 시한 효과 = 라운드(주인 진영 턴 시작마다 −1) · 피해 적용 `Hit(d, dmg, by, HitKind)` = 무적→무효화→호위→부활→흡혈·명중 상태 이상→처치 보상 · 재행동 `ReactCredits` · 즉시형 · `TurnBegin(side)` = 상대 턴 끝 회복·정화 + 중독·화상·회복·지대 · 행동 중 탭 `WaitTap`(조조·하후돈 · 테스트 `AutoTap`) · 지대 `SkillZone`) · `SkillStatus`(혼란·출혈·중독·화상 · 기절 = 기믹 `GimmickLayer.Stun` 재사용) · 특수 이동 = `ActionResolver`(관통·핀볼·복귀·교환·끌어오기·난동·반복) · 특수 사격 = `ActionResolver.Skills.cs`. 가상 실행 = `SkillSystem.TakeState/RestoreState`(Sim). PD 결정 18건(2026-11-10 · 대화로그 `2026-11-10_스킬구현.md`): 「1 m」 = 전장 1 칸 · 「행동 반경」 = 기본 슬링 거리 · 기획에 없는 지속 턴 = 1 · 출혈 = 슬링 최대 거리 이동 시 최대 HP 50 % · 망령 = 다음 자기 턴 시작에 1번 공격 후 소멸 · 육손 불 장벽 = 아군도 · 손건 화친 = 피해 없이 회복만 · 보병 목표점 추가 이동 제거. 테스트 = `Tests/PlayMode/SkillTests`(효과 묶음 26 · PD 결정·Fable 검토 반영 포함). 캡처 = `-executeMethod TKS.EditorTools.DataImporter.CaptureSkills -skillDir <dir>`.
- **아웃게임 2차(2026-11-05~06 #1093 C)** — 표 10장 추가(`training` · `training_ranks` · `patrol` · `dev_tree` · `treasures` · `treasure_sets` · `chests`(+ iron/buy 열) · `tower` · `dungeons`(+ reward_amount) · `arena`(+ 상점 키)) + `economy.csv` C 키 24 → 임포트 자산 18(A 9 + C 9 · `OutGameTables.C.cs` 검증). 코드 = `OutGame/Domestic/`(`TrainingRules` 훈련 · `PatrolRules` 순찰 · `DevRules` 개발 효과 키 → 적용 지점 · `DomesticScreen`) · `OutGame/Treasure/`(`TreasureRules` 보유·세트·강화·조건 카운터·상자 · `TreasureScreen`) · `OutGame/Arena/`(`ArenaRules` 탑·던전·투기장·적장 · `ArenaScreen`). 스탯 = §3-2 × (1 + 훈련(병종·항목)) × (1 + 보물(항목)) = `HeroRules.Compute(…, save)` · 보물 crit/range/splash = `BattleManager.SideEffectsProvider` → `SkillMods.Get` 진영 효과(`side_crit_pct` · `range_mult` · `splash_radius_mult`) · 조건 효과 카운터 = `Runtime/BattleEvents.Raise`(전투 코드 1줄씩 · 아군만) · 연무장 전투 = `BattleManager.StartMode(BattleMode)`(적 편성 · 제한 턴 · 결과 탭 = `onExit`) → `OutGameSession.OnModeEnd`. 테스트 = `Tests/PlayMode/OutGameCTests`. 캡처 = `-executeMethod TKS.EditorTools.DataImporter.CaptureC -cDir <dir>`(사본 · 10장).
- **영웅 72명(PD #1108 · 2026-11-10)** — heroes 87행(영웅 72 + 병사 15) · skills 144 · skill_effects 288 · 옛 키 체계(51+4종 · 견제·관통 배율 등)는 #1112 에서 폐기.
- **지형 기믹 레이어(PD #1097 · 2026-11-06 세션 D)** — SOT = `Assets/Data/Gimmicks/{gimmicks,stage_gimmicks,tutorial_tips}.csv`(기획 `tables/` 사본 · utf-8-sig) → 메뉴 `TKS/Import Gimmicks (기믹 임포트)`(`Editor/GimmickImporter` · 오류 시 자산 불변 · `ai_weights.csv` 도 함께) → `Resources/Gimmicks/GimmickDb`·`StageGimmickTable` · `Resources/AI/AiWeights`. 전투 시작 = `GimmickLayer.Build(stage)`(장-번호 배치 · 배경 위 · 유닛 아래 · 장애물 = 콜라이더 + `GimmickBody` · 지대 = 모양 스윕 · 강 = 늪처럼 통과하는 지대(강 안 ×p3 · 다리 구간 = 효과 없음 · 강가·다리에서 밀려 강물에 들면 빠짐 · PD 2026-11-06 개정) · 겹친 유닛은 빈 자리로). 전투 코드는 질의만: `Combat.FreeTravel`(장애물 = 벽 · 숨은 함정 정지) · `FirstUnitOnArrow`(pass/block/destroy) · `Combat.Damage`(군기 공 · 고지 근접 피격 · 안개 원거리 피격 ×p1 · 책사 광역이 그 반경 안개를 걷음) · `SkillMods.Range`(고지 사거리) · `BattleManager` 턴 시작 `TurnStart`(군기·중독·불·행동 불가). 충돌 반응·폭발·굴림·미끄러짐·지대 = `Gimmicks/ActionResolver.Gimmicks.cs`(partial). 배치 도구 = `TKS/Gimmick Painter (기믹 배치)`. 테스트 = `Tests/PlayMode/GimmickTests` · 캡처 = `-executeMethod TKS.EditorTools.GimmickImporter.Capture -gimmickDir <dir>`(사본).
- **병종 규칙 개정 + 예측 AI(PD #1098 · 2026-11-06)** — 보병 = 적과 싸운 뒤 튕겨나는 길의 다른 적과 연쇄 교환 1회(`ActionOutcome.Chains`) · 창병 = 상대만 밀리고 자신은 × `spearRecoilRatio` 반대로(밀며 전진 폐기). 적 턴 = `Runtime/AI/TacticalAI`(후보 방위 × 파워 3 × 액티브 → `Sim` 가상 실행(연출 없음 · 분산 0 · 상태 복원) → `ai_weights.csv` 점수 · 서포터 → 돌격 → 사격 · 1기마다 재평가) · 가중치 자산이 없으면 `EnemyAI.Decide`(기존 조준) · 적 액티브 = 1부 보스만(`boss_active`). 가상 실행 중(`Sim.Active`)에는 `Fx`·`UnitView`·`BattleEvents`·HUD 갱신이 꺼진다 — 새 연출 코드도 `Sim.Active` 면 건너뛸 것. 조준 미리보기도 같은 가상 실행(`GimmickPreview` · 숨은 함정 모름). 테스트 = `Tests/PlayMode/TacticalAITests`.
- 아웃게임 코드 = `Assets/Scripts/OutGame/**`(asmref → TKS.Runtime) — `OutGameSession`(표 + 세이브 · 바꾸는 동작은 즉시 저장) · 규칙 `HeroRules`·`EconomyRules`·`GachaRules` · 화면 `UI/`(로비·영웅·편성·거래·결과 패널 · `OutGameRoot` 가 전환). 세이브 = `persistentDataPath/save.json`(테스트·검증 캡처는 `SaveStore.UseMemory` / SessionState `TKS_SAVE_MEMORY` 로 메모리 전용). 출전 = `BattleManager.LineupProvider`(편성 프리셋 → `playerSpawnPoints` 자리 0~2 전열 · 3~4 후열). 화면 캡처 = `Unity.exe -projectPath <사본> -executeMethod TKS.EditorTools.DataImporter.CaptureOutGame -outgameDir <dir>`. **UI 수정 검증은 고친 화면만 찍는다**(PD 2026-11-09 「매번 모든 화면 체크 = 시간·토큰 낭비」): `Tools/mirror_verify.py` → `Unity.exe -projectPath E:/Nerdnavis/_tks_verify -force-d3d11 -executeMethod TKS.EditorTools.DataImporter.CaptureUI -uiDir <dir> -uiOnly hero_list,hero_detail*`(이름 = 정확히 또는 「접두*」 · 다 찍으면 바로 종료 · 1장 ≈ 15초 · 전체 21장 ≈ 3분 · 이름 목록 = `UICaptureDriver` 의 Shot 이름).
- **챕터·스테이지 SOT = `Assets/Data/Stages/*.csv`(utf-8-sig · 2026-11-05 145 스테이지 · 기획서 #1086 §4 전사)** — `stages.csv`(chapter·stage·title·type normal|elite|boss·place·faction·병종 4 수·generals `id:병종:true|false`·bg_theme·note) · `chapters.csv`(장 제목·연도·**월드맵 맵 id**·AI 출발값) · `generals.csv`(무장 id → 이름·배율 덮어쓰기) · `factions.csv`(세력 코드 → 이름·병사 아트 접두). 고친 뒤 메뉴 `TKS/Import Stages (스테이지 임포트)`(`StageImporter` · 형식 오류면 자산 미변경) → `BalanceConfig.chapters·stages`. 적 좌표 = `BalanceConfig.formation` 규칙(전위·중군·후군·장수 줄 · `StageTable.Enemies`) · 장수 배율 = `generals.csv` 가 비면 `boss/eliteGeneral*Mult`. 월드맵 노드 = 스테이지 표(챕터별 수 · 보스 = type boss) · 노드 자리 = `ChapterMap/Data/<맵>.json` `slots_N`(없으면 길 위 균등).
- 규칙 해석 변경 = `Assets/Scripts/Runtime/ActionResolver.cs`(병종 4종) · `BattleManager.cs`(턴·승패·창병 대기) · `Combat.cs`(피해 공식·경로 질의). 변경 시 `Assets/Tests/PlayMode/BattleFlowTests.cs` 에 테스트 추가 후 22종 이상 전부 통과 확인.
- 콜라이더 질의 전 `Physics2D.SyncTransforms()` · 유닛 제거는 `SetActive(false)` 후 `Destroy` · 자식 렌더러가 있는 표시물은 GameObject 통째로 끈다.
- **궁병 규칙(PD 2026-11-04 · #1080)**: 사거리 = `slingMaxDistance`(얇은 원). 조준 방향 사거리 안의 적(가장자리 간격 기준) = **제자리 사격** · 사거리 밖 ~ ×(1 + `archerRangeRatio`)(붉은 도넛) 안의 적 = 들어올 만큼만(최대 `ArcherMoveAllow` = 슬링 × 비율) 다가가 사격 · 더 멀면 **목표점까지 이동만**(#1079). 반격 없음 · 목표점 명중 크리. **근접 전투**(돌진·반격·관통·견제·밀려 닿은 공격)에서 궁병은 근접 유닛 상대 주는 피해 × `archerMeleeDealtRatio`(0.5) · 받는 피해 × `archerMeleeTakenRatio`(1.5) — `Combat.Damage(…, melee)` · 원거리 사격은 패널티 없음.
- **충돌 규칙(PD 2026-11-04 · #1080)** — `ActionResolver`: 보병 = 경로 첫 유닛(아군·적군)에 부딪히면 **알까기**(등속 접근 → 부딪힌 순간 피해·반격을 즉시 교환(`ImpactExchange`) → **양쪽이 동시에 튕김**: 상대는 접촉 방향으로 남은 거리 × `collisionPushRatio` · 자신은 반대 방향으로 남은 거리 × `collisionSelfBounceRatio` · PD #1081) · 기병 = 유닛 무시 관통(적 피해 · 아군은 지나칠 거리가 안 나오면 **그 아군 직전 정지** `StopBeforeUnclearedAllies`) · 창병 = 첫 유닛을 **크게 밀치고 자신은 적게 밀림**(남은 거리 × `spearPushRatio` 1.5 · 반작용 = 그 × `spearRecoilRatio` 0.2 · 적이면 피해 · 아군은 밀기만 · PD #1098 · 2026-11-10) · **규칙 ⑤** = 밀린 같은 진영 유닛이 상대와 닿으면(`Combat.TouchingOpponent` · 간격 ≤ 0.12) 그 자리에서 근접 공격 1회(조작마다 · 자기 행동 소모 없음 · `ActionOutcome.Notes`). 적 AI 도 같은 코드(대칭). `UnitStats.attackRange` = 옛 창병 견제 반경(견제 제거로 미사용).
- **사거리 표시(PD 2026-11-04)**: 유닛 탭(아군·적군) → `RangeIndicator`(슬링샷 최대 거리 = 얇은 반투명 원 · 궁병 = 슬링샷 범위 ~ ×(1+비율) 채워진 붉은 도넛 · 범위 안 적이 없으면 회색).
- 보고는 실측·추정·미확인을 분리한다(C23 · C5). 커밋은 의도 변경 파일만.
## UI 캔버스·아틀라스 구조 (PD 2026-11-10 「리소스·정보가 얼마나 자주 바뀌는지에 따라 캔버스를 나누어 최적화 · 드로우콜 최소화 구조 필수」)
UI 를 새로 만들거나 고칠 때 **반드시** 이 구조를 따른다(어기면 `Tests/PlayMode/UILayerTests` 가 실패). 조직 전체 표준 = 조직 레포 프로젝트 규칙 **P41**(`.claude/skills/너드나비스-코어룰/rules/P41.md` · 2026-11-10) — 이 절은 그 표준의 이 프로젝트 구현 SOT 이자 다른 프로젝트의 참조 구현이다.
- **갱신 등급 3단**(`Runtime/UI/UiLayer.cs` `UiRate`): **Static** = 화면을 열 때만(바탕 · 틀 · 고정 글자) · **Event** = 조작·게임 사건 때(값 · 선택 · 턴 · 팝업) · **Frame** = 매 프레임(호흡 · 튀김 · 스크롤 내용 · 끌기 · 미니맵 · 커서). 등급이 다른 것은 같은 캔버스에 두지 않는다 — 캔버스는 자식 하나만 바뀌어도 그 캔버스 전체를 다시 묶는다.
- **만드는 법**: 루트 = `UiLayer.Root(go, cam, order, plane, reference, rate)` · 기존 오브젝트를 층으로 = `UiLayer.Nest(go, rate, raycast)` · 빈 층 = `UiLayer.Group(name, parent, rate)` · 매 프레임 요소 = `UiLayer.Isolate(go)`(`UiPulse`·`UiPop` 은 스스로 호출 · 바로 위 부모가 Frame 층이면 그 층에 합침). 중첩 캔버스는 **overrideSorting 끔**(그리는 순서 · `RectMask2D`/`Mask` 자르기 유지) · **누르는 그래픽이 있는 층에만 GraphicRaycaster**(중첩 캔버스의 그래픽은 부모 캔버스 레이캐스터가 못 봄 → 누름 층은 `raycast: true`).
- **현재 배치**: 아웃게임 루트(Static · 바탕 + 하단 탭) → 화면마다 중첩(Event) → `UiKit.ScrollGrid` 내용(Frame · 자동) · 위 덮개 루트 → 재화 바 · 팝업(`ModalCustom`) · 토스트 · 뽑기 결과 · 편성 끌기 유령 각각 중첩 · 전투 결과 루트(Event · 별 줄 = Frame) · 전투 HUD 루트 = 정적 판(칩 바탕 · 유닛 판 바탕 · 미니맵 틀) → `Info`(글자·값·스킬 버튼 · Event) → `Minimap/MinimapLive`(시야·점 · Frame) · `SkillGlow`(Frame) → `Result`(Event) · 월드맵 루트(Static · 띠·넘김·본영) → `Content`(맵·노드 · 스크롤 · Frame) → `cursor`(Frame) · `Popup`(Event).
- **매 프레임 호출되는 갱신 코드**는 값이 같으면 쓰지 않는다(`sizeDelta`·`anchoredPosition`·`CanvasGroup.alpha/interactable` 비교 후 대입 · `Text.text`·`Graphic.color` 는 Unity 가 이미 비교).
- **아틀라스**: 구성 SOT = `Assets/Data/UI/ui_atlas.csv`(theme 줄 = UI 테마가 쓰는 스프라이트를 경로 match 로 · folder 줄 = 폴더 통째 · `single_area` 보다 큰 그림 = 묶지 않음) → `Editor/UIAtlasBuilder` → `Assets/UI/Atlases/*.spriteatlasv2`(UI_Common 4096×2048 · UI_Content 2048×2048 · WorldMap_UI 1024×1024 · 각 1장). **UI 테마 임포트가 끝날 때마다 자동 갱신**(새 납품 스프라이트 = 코드·표 변경 없이 묶임) · 수동 = 메뉴 `TKS/Build UI Atlases (UI 아틀라스 갱신)`. 패커 = Sprite Atlas V2(에디터 Play 에서도 묶임 · `EditorSettings` m_SpritePackerMode 5). 사각 묶음(Tight 끔 · 회전 끔 — 9-slice·Filled 보존). **스프라이트를 잘라 새로 만드는 코드는 `sprite.packed ? sprite.textureRect : sprite.rect`**(`UISkin.WorldSprite` 참고 · `rect` 는 원본 기준).
- **검증**: `-executeMethod TKS.EditorTools.DataImporter.CaptureUI` 캡처 로그에 화면마다 `layers <화면> batches=… canvases=루트+중첩 … frameRebuildGraphics=…` 줄 + `ui_layers.csv`(캔버스별 그래픽 수 · 등급 · 레이캐스터 · 매 프레임 요소 여부 · `Runtime/UI/UiLayerProbe`). 2026-11-10 개선 전→후(13화면 · 1080×1920) = 배치 합계 1124 → 439 · 평상시 매 프레임 재구성 그래픽(전투 HUD 49 → 10 · 월드맵 241 → 1 · 보물 상자 115 → 2 · 전투 결과 115 → 14).
- **남은 드로우콜 원인**(2026-11-10 실측): 영웅 목록(72)·편성(120) = 유닛 초상(유닛 아트 프레임 797×826 · 유닛마다 다른 텍스처라 카드마다 끊김) — 목록용 축소 초상 아틀라스 = **모든 영웅 리소스 제작이 끝난 뒤 진행**(PD 2026-11-10 결정 · 그 전에는 만들지 않는다).
- 같은 PC 의 다른 세션도 `E:\Nerdnavis\_tks_verify` 를 캡처에 쓴다 → 사본은 세션마다 따로(예 `Tools/mirror_verify.py <본체> E:/Nerdnavis/_tks_verify_ui`) · 남이 돌리는 사본에 미러하지 않는다(실행 중 코드가 바뀜).
## 스킬 이펙트 작업 (2026-11-09 PD 「WL 에서 스킬을 구현하던 방식과 동일하게 유닛별 스킬 구현」 · 이 PC = 연출 담당)
- **작업 착수 규칙(PD 2026-11-10)**: 지시가 오면 첫 명령 = `unity command --project-path . editor.playmode stop`(씬이 열려 있든 Play 중이든 끄고 바로 착수 · 기다리지 않음) · 지시를 받은 작업은 끝까지 진행(멋대로 멈추지 않음 · 도구 호출이 끊기면 다시 걸어 이어 감).
- **확인 규칙 = 수정한 부분만(PD 2026-11-10 「수정 지시마다 수정한 부분 외 모든 부분을 다 테스트하는 것 같다 → 수정한 부분만」)**: Play 캡처는 그 지시로 바뀐 이펙트·유닛·상황만(예: 「기병 복귀 재생」 = 기병 1기 돌격·복귀만 · 다른 진영·방향·이펙트 재확인 금지) · 자동 테스트는 전투 규칙 코드(ActionResolver · Combat · BattleManager 판정 · SkillSystem 효과)를 바꿨을 때만 그 관련 테스트 클래스만(`test run --filter`) · 표(csv) · 이펙트 사본 · 셰이더 · 테스트 씬 UI 만 바꾼 지시는 자동 테스트 없음 · 전체 테스트(118)는 PD 가 커밋을 지시할 때 1회.
- **스킬 테스트 씬** = `Assets/Scenes/SkillTest.unity`(메뉴 `TKS/Skill Test/Build Scene` 로 다시 만듦 · 씬 = `SkillTestBootstrap` 하나) — 전투 코드 그대로 + 월드맵·아웃게임·세이브 없음 · 왼쪽 위 「유닛 선택」(heroes.csv 영웅 56 · 진영별) · 「이펙트 보기」(에디터 전용 · `Assets/VFX` 프리팹 검색 → 고른 영웅 몸에 띄움 · 크기 ×0.25~4 · 기울기 0/−45/−90 · 방향 · 경로 복사) · 왼쪽 아래 「다시 배치」(표 다시 읽기 + 처음 자리) · 쿨 무시 · 더미 행동 · 영웅 무적. 더미 = 죽지 않음(`Unit.Invincible` · 아군 턴마다 체력 회복) · 배치 표 `Resources/Skills/skill_test.csv`(hero 행 n_slot + 상대 좌표) · 더미 행동 끔 = `BattleManager.EnemyHold`. 패널 위 누름은 슬링샷이 무시(`SlingshotInput.BlockPress`).
- **이펙트 값 = 표 `Assets/Resources/Skills/skill_vfx.csv`**(머리 주석이 칸 설명) → `Runtime/Skills/SkillVfx`(전투마다 다시 읽음). 키 = 스킬 id(액티브) 또는 `basic_<병종>`(일반 슬링샷 공격 · 이동만인 추가 이동 제외) · 이벤트 = cast(발동) · travel(슬링샷 돌진 동안 · `f_every` 반복) · hit(그 행동의 피해 순간 · 대상마다 1번). 훅 = `SkillSystem.Announce`(cast) · `BeginAction`/`EndAction`(travel) · `Hit`(hit) · 즉시형 `Instant`. 크기 `f_scale` = 유닛 키 대비(WL 배율 ÷ 1.8 ≈ 같은 비율) · 입자 크기 모드는 띄울 때 전부 Hierarchy · 정렬 = 12 + n_order · 가상 실행(Sim) 중 없음. **돌진(travel) 규칙 = WL `WLStaffEarth.Rush` 와 같게**(2026-11-09 PD 「돌진하는 동안 이펙트가 정상적으로 안 보임」 수정): `b_sustain` 1 = 시간당 방출 층을 반복으로(한 번 띄워 돌진 내내 유지 · 다시 띄우면 0.1~0.2 s 만에 끊김) · `b_clear` 1 = 행동 끝에 남은 입자까지 지움(WL ClearEnd) · `b_face` = 이펙트를 옆모습으로 화면에 눕혀 진행 방향으로 돌림(`SkillVfx.FxRotation` · 3D 세계 그대로 놓으면 위쪽 돌진 때 판 모양 섬광이 옆면만 보임) + `f_tilt` = 진행 축 기준 카메라 쪽 기울기(−35 기본 · −90 = 위에서 본 모습) · 이 게임 돌진은 약 0.3~0.4 s(WL 0.95 s)라 배속은 그 길이에 맞춘다. **2026-11-10 추가**: `s_skill` 은 `;` 로 여러 키 · `s_camp` = 진영(heroes.csv faction shu·wei·wu·other · `Unit.Camp` = 출전 영웅만 · 진영 없는 적 병사 = other) · `f_fwd` = 진행 방향 앞(+)·뒤(−) 위치 · `f_drift` = 입자 자체 이동 배율(스스로 날아가는 데모용 돌진 이펙트를 유닛에 붙일 때 0.1) · 회전 = **프리팹 원본 루트 회전을 살리고 그 진행 축만 화면 방향에 맞춤**(`FxRotation(tilt, face, r0)` — MasterStylizedFX 투사체·Dash 는 루트에 (0, 90, 270) 같은 회전이 있어 덮어쓰면 판이 옆으로 서서 가늘게만 보임) · `n_order` −14 = 유닛 바로 아래(캐릭터가 이펙트 안에 보임 · 바닥 −20~−16 · 사거리 원 −10 · 유닛 0) · 팩 데모용 자리표시(`Character` 큐브)는 사본에서 끈다. 현재 시험(PD 2026-11-10 스크린샷) = 보병·창·기병 돌진: 위 `TKS_Fx_EnergyProjectile`(파랑) · 촉 `GreenProjectile` · 오 `FireProjectile`(주황) · 군 `PurpleProjectile` + 뒤쪽 대쉬 느낌 `TKS_Fx_<Blue|Green|Red|Purple>Dash`(f_fwd −0.6). **URP HDR 근사**: MasterStylizedFX 이식 셰이더는 `_BuiltinPorts/TKS_HdrApprox.cginc` 로 1 넘는 밝기를 흰빛으로(빌트인 · 감마 · 블룸 없음이라 그대로면 평평한 단색 띠).
- **돌진 이펙트 재생 구간 · 소멸(PD 2026-11-10)**: travel 이펙트는 「슬링샷 직진 구간」 동안만 — 보병·창병 = 접촉 순간, 기병 = 관통 끝에 `SkillSystem.DashEnd` 로 멈추고 `f_fade` 초 동안 그 자리에서 흐려져 사라짐(`SkillVfxFade` · 입자 색 알파를 직접 낮춰 수명 무한 층도 사라짐) · 기병 복귀 · 위진소요 추가 관통 = `DashBegin` 으로 그 방향에 다시 재생 → 도착 후 같은 소멸(훅 = `ActionResolver` 의 `DashEnd/DashBegin` 5곳). 튕김 · 밀기 · 반작용 = 반응이라 이펙트 없음.
- **이중 레이어(PD 2026-11-10 「돌진 이펙트 앞부분(머리)은 캐릭터보다 상단 · 레이어 이중 · 돌진 방향만 실드 형태로 일부를 가림」)**: 표 `s_over` = 유닛 위(정렬 12)에 그릴 자식 층 이름(`;` 여러 개) · 나머지 층은 `n_order` 그대로 — 진영 혜성 = `BG;Head (1)`(머리 셸) 위 · 꼬리 −2 · 뒤쪽 Dash −3 · 머리 중심 = 몸 앞 0.6 키(0.2 키 = 상반신을 다 덮음 · 실측).
- **IB(쿠로네코) 이펙트 시험(현재 기본 공격 · PD 2026-11-10 「내가 원한 비주얼은 이런게 아니야(이노센트베인 영상) → 우선 IB 이펙트를 그대로 써서 확인 · 나중에 구조에 맞게 새로 그림(화풍 통일)」)**: IB 슬링샷 발사 `UnitVisualEffectControl.ShowBoosterFX()` 와 같은 구성 — `ef_booster`(앞 빛 물결 2장 + 가시 · 0.2초 반복 맥동 · 유닛 위 · 이동 끝에 바로 지움 = IB Breaking 때 Kill) · `ef_speed_tail`(꼬리 0.6초 + 화살표 입자 · 유닛 아래 · 끝나면 꼬리가 줄며 사라짐 — `SkillVfx.Stop` 이 트레일 길이만큼 기다림) · `ef_booster_dust`(출발 먼지 1번) · 타격 `ef_normal_hit`. 원본 = `Assets/VFX/IB/`(쿠로네코 `E:\NerdNavis\Kuroneko\KuronekoClient` 에서 `Tools/wl_vfx_import.py copy … --wl <쿠로네코> --dest Assets/VFX/IB --skip-scripts` · GUID 유지 · 무수정) · 사본 = `Resources/Fx/TKS_Fx_IB_*`(`Tools/make_ib_fx_copies.cs` — IB 전용 스크립트 부품 제거 · 정렬 레이어 Default · 방향 있는 것은 감싸는 루트 + 안쪽 Z −90°(IB 정면 +Y → 이 게임 +X) · 소리 이벤트 받는 곳 `IbFxAnimEvents`(아무것도 안 함 · 없으면 재생마다 콘솔 오류)). 셰이더 = `Fingar/Additive`(더하기 섞기 · 빌트인 그대로). 2D 돌진 3차 행은 표에 `#` 주석(되돌리기). **진영색(PD 「진영 색을 입히고」)** = `TKS_Fx_IB_Booster_<진영>`(스프라이트 색의 색상만 진영색 · 채도·밝기 유지) · `TKS_Fx_IB_SpeedTail_<진영>`(꼬리 그림 `Assets/VFX/IB/_TKS/Tail_Basic02_<진영>.png` = `Tools/make_ib_camp_textures.py` · 재질 같은 폴더 · 화살표 입자 색) — 둘 다 `make_ib_fx_copies.cs` 가 `Tools/dash2d_camps.csv` 로 만듦 · 원색 사본 `TKS_Fx_IB_Booster` · `SpeedTail` 은 비교용으로 남음(표에서 안 씀). **사라지는 시점(PD 「멈춘 이후 뒤늦게 이펙트가 사라짐 · 위치 안 맞음」)** = 속도 꼬리 `f_fade` 0.12 — 멈춘 자리에서 꼬리째 흐려짐(`SkillVfxFade` 가 트레일 색 그러데이션 불투명도도 낮춤 · 전에는 0.6초 꼬리 머리가 기병 돌격 끝자리에 남아 복귀 중에도 보였음). **타격** = 현재 없음(PD 2026-11-10 「적이 피격되었을 때 히트 이펙트는 제거 · 어떤 히트 이펙트를 쓸지 알려줄게」 · IB NormalHit · BounceHit 행은 표에 `#` 주석). **기준점 `e_anchor` = dash**(빛 물결 · 꼬리 · PD 「복귀할 때 이펙트가 캐릭터보다 위쪽에 생겨 안 보임 → 유닛 바닥에」) = 위·옆으로 갈 때 몸 가운데 · 아래로 갈수록 발 쪽(곧장 아래 = 발). **흙먼지** = WL 창 돌진 흙먼지 `TKS_Fx_SpearRushDust`(0.15초마다 발밑 · PD 「복귀할 때 흙먼지가 너무 세서 뭉개짐 → WL 흙먼지로」 · IB 출발 먼지 행은 `#` 주석).
- **방향 결정 UI = IB 이식(PD 2026-11-10 「IB 와 같이 슬링샷 조작 시 방향 결정 UI 등을 그대로 이식」)**: `TKS.IbAimArrow`(IB `GameTouchArrow` 동작 · 프리팹 `Resources/Fx/TKS_Fx_IB_GameArrow` = 원본 `Assets/VFX/IB/AssetResources/Game/GameArrow` 사본) — 누르면 유닛 둘레 고리 + 퍼지는 고리 3개(3초 · 1초 간격) · 당기면 하늘색 화살표가 쏠 방향으로 돌고 당긴 세기만큼 길어짐(0.5~1.5 · 최소 당김 → 슬링샷 최대 거리) · 화살 끝이 전장 밖이면 안으로 · 놓으면 숨김. 전에 쓰던 조준선 · 당김선은 끔(`SlingshotInput.ShowPreview` · IbAimArrow 가 없을 때만 예전 선). 목표 고리 · 강조 · 궁병 화살 경로선 · 기믹 예상 궤적은 그대로. 화살표 색 = IB 공격 종류 3(하늘색) 하나.
- **여포 크기(PD 2026-11-10 「왜 여포는 다른 기병보다 작지?」)**: 말 탄 유닛은 머리 끝 상한(BalanceConfig.mountedArtMaxTop 1.3 × 표준 키)에 맞춰 줄어드는데, `dz_boss_lvbu` 의 몸 높이(`meta.json` bodyHeightPx · `art_size.csv` body_px)가 머리 깃털 끝까지 819 px 라 몸 전체가 더 줄었음 → 왕관 끝 ~ 발 718 px(front_idle 실측 · 깃털 제외 · 다른 기병의 투구 끝 기준과 같은 규칙)로 고침 → 배율 0.2381 → 0.2716(+14 %). 원본 백업 = 세션 scratchpad. **다른 PC 에서 그림을 다시 export 하면 819 로 되돌아갈 수 있음**(export 규칙 = 불투명 폭 30 % 행 — 깃털이 넓어서 안 걸러짐).
- **근접 3병종 특징 = 한 줄 정의(PD 2026-11-10 직접 지시)**: **보병** = 적을 밀치고 튕겨나며 튕기면서 다른 적을 추가로 공격(알까기 + 연쇄 · #1098 그대로 · 목표점 명중 추가 이동 = 제거(PD #1112 결정 ⑱)) · **창병** = 적을 크게 밀치지만 자신은 적게 밀림(`spearPushRatio` 1.5 > 보병 `collisionPushRatio` 1.0 · 반작용 남은 거리 × 0.3 < 보병 자기 튕김 0.5) · **창병 대기(견제) 제거**(「유저가 인지하기 어려움」 · 목표점 명중해도 `SetOverwatch` 안 함 · HUD 「견제 반경·대기 중」 문구 삭제) · **기병** = 적을 관통해 이동 · **목표점 명중 시 제자리 복귀 제거**(「유저가 인지하기 어려움」 · `PreviewCavalryReturn` · 조준 때 복귀 영역 `ShowReturnZones` 삭제 · AI 가중치 `cav_return`·`cav_stranded` 삭제). 견제·복귀 스킬 = #1112 PD 새 기획으로 전부 재설계(견제 없음 · 복귀는 스킬 효과 `return_on_hit`·`return_always`·`return_aim` 으로만) · `SkillMods.Overwatch*` 삭제 · `BattleManager.ApplyOverwatch` · `Unit.Overwatch` 는 스킬 확장 없이 남음(대기 유닛이 없어 실행 안 됨).
- **IB 크기 · 캐릭터 연출(PD 2026-11-10 「IB 와 화살표 크기도 다르고 선택된 캐릭터 주위 연출도 누락」)**: 값 표 `Resources/Skills/ib_ui.csv`(`TKS.IbUi`) — IB 유닛 반지름 0.496(ExportCharacter size 8 × 0.062) 기준 배율 = 이 게임 유닛 반지름 / 0.496 → 고리 지름 = 반지름 × 5.4 · 화살표 세기 s = IB 월드 당김 × 0.6(0.5~1.5) · IB 취소 거리 0.5. **`TKS.IbUnitMarks`** = 선택한 행동 가능 아군 발밑 도는 원(`TKS_Fx_IB_TargetCircle` · IB ShowUserTurnMark) + 몸 둘레 빛(`TKS_Fx_IB_AttackReady`) · 조준 중(누름 ~ 놓음) 모든 유닛 속성 방울(`TKS_Fx_IB_Attribute_<속성>` · IB SetActiveBarrierVisualEffect · 진영 → 속성 = 표 element_*: 위 물 · 촉 나무 · 오 불 · 군·진영 없음 어둠).
- **타격감(PD 2026-11-10 「화면 흔들림 등도 IB 를 참고해서」)**: `TKS.HitFeel` + 표 `Resources/Skills/hit_feel.csv`(행동 중 유닛이 상대에게 처음 피해를 준 순간 · `SkillSystem.Hit` · 흔들림 = `CameraController.Shake`(추적 위치는 그대로 두고 흔들림만 더함) · 히트 스톱 칸 있음(현재 0)). **IB 실측**: 흔들림 함수 6종은 스킬·궁극기·기믹 이펙트 애니메이션 이벤트로만 불리고 본문이 디컴파일에서 비어 있어 값 없음 · 일반 타격·벽 튕김엔 흔들림 없음 · 히트 스톱 없음 → 표 값(hit 0.15초 · 키 10 % / 기병 0.18초 · 13 %)은 추정.
- **2D 돌진(PD 2026-11-10 「기본 공격(돌진) 이펙트가 너무 3D 느낌 → 2D 처럼」 · MSFX 혜성 행은 표에 `#` 주석으로 남김 = 되돌리기)**: **3차(현재 · PD 2026-11-10 「검기는 이전 형태가 더 나음 · 대쉬 이펙트는 캐릭터 몸을 벗어나지 않도록 · 첨부 같은 느낌으로 몸을 뒤덮는 연출을 스킬용 시험」)** = 초승달 `Head` = 1차 그림 `dash2d_crescent`(검정 외곽선 · 진영색 곱) · 대쉬 층(꼬리 0.04초 · 바람 줄기 · 알갱이 · 흙먼지) = 유닛을 따라 움직이는 Local 공간 · 몸 기준점 반경 0.45 키 안(실측 그림 경계 좌우 ±0.56 · 아래 −0.60 ~ 위 +0.59 키) · 바람 줄기 `Lines` · 알갱이 `Dots` = 유닛 위 반투명(표 `s_over` = `Head;Lines;Dots` · 유닛 아래면 몸에 다 가려 안 보임 · PD 「몸의 대쉬 효과가 왜 안 보이나」) · 꼬리 · 흙먼지 = 유닛 아래 · **기병 복귀 = 돌아서서 감**(`UnitView.TurnAround` · 복귀 방향으로 보기 + 곧장 위·아래면 좌우 반전 · 도착 뒤에도 마지막 이동 방향 PD #1082) · **몸 뒤덮는 기운(시험)** = `TKS_Fx_Aura2D_<진영>`(혜성 기운 `aura_comet_<진영>` 겹쳐 깜빡임 · 불꽃 혀 `aura_wisp` · 반짝임 = 유닛 아래 · 옅은 막 `Veil` · 앞 테 `Rim`(2차 검기 그림) = 유닛 위) · 표 키 `test_aura` · 테스트 씬 「기운 시험」 스위치(`SkillVfx.TestTravelKey` · 켜면 슬링샷 공격 돌진에 함께 · 실제 전투 = 늘 빈칸). 2차 = 외곽선 없음 · 그림 `Assets/VFX/TKS2D/`(만들기 = `python -I -X utf8 Tools/make_dash2d_textures.py` · 진영색 원본 = `Tools/dash2d_camps.csv`) → 프리팹 `Resources/Fx/TKS_Fx_Dash2D_<wei|shu|wu|other>`(검기 초승달 `Head` = 진영별 그림 `dash2d_slash_<진영>` · 유닛 위 · 반투명 꼬리 `Trail`(앞 0.7 → 0) · 흰 바람 줄기 `Lines` · 알갱이 `Dots` = 유닛 아래 · 수명 동안 투명해짐) + `TKS_Fx_Dust2D`(부드러운 반투명 흙먼지 · 모든 진영) · 다시 만들기 = `unity command … script eval --file Tools/build_dash2d.cs`(값을 바꾸면 이 파일과 프리팹을 함께). 1차(검정 외곽선 셀 그림)의 `dash2d_crescent.png` · `dash2d_highlight.png` 는 안 씀. 표 `b_flat` = 1 이면 화면 안에서 진행 방향으로만 돌림(기울기·팩 루트 회전 무시 · `SkillVfx.FlatRotation`) · 크기 단위 = 유닛 키 1(`f_scale` 1) · 끝 소멸 = `f_fade` 0.12(`SkillVfxFade` 가 스프라이트 불투명도도 낮춤).
- **액티브 스킬 1차 연출(PD 2026-11-10 「fable 제안 이펙트 연출을 구현까지 1차로 · 테스트 씬에서 하나씩 피드백」)**: Fable 제안(조직 레포 `공유/기획팀_산출물/삼국지슬링샷/스킬연출_1차_Fable제안_2026-11-10.csv` · 56 스킬 · 138행 · 기믹 14유형) → `skill_vfx.csv` 스킬 id 행(메모 = 원본 경로 · 이유) · 사본 `Resources/Fx/TKS_Fx_Sk_<원본 이름>`(131) + 진영색 안 맞는 10개 = 영웅 전용 보정 사본 `…_<진영>` · WL 추가 이식 1(서황 hit `FX_Whirlwind Ax`). 점검 = 사본 누락 0 · 셰이더 오류 0 · 짧은 이펙트 8 정상 방출(실측). **알려진 한계(PD 확인 대상)**: 아군 전원·적 전원 효과는 시전자 위치에만 표시 · 피해 0 인 매혹·밀기는 hit 이 안 뜰 수 있음 · 장합 변진 도착 연출 지연(f_delay) 없음 · 서황 도끼 뒤 판 메시(`Plane_Trail`)가 정지 시 사각형으로 보일 수 있음 · 색은 YAML 근사 판독(Fable).
- **크기 = 유닛에 맞춤(PD 2026-11-10)**: 혜성 머리 폭(진행 방향에 수직) = 유닛 키 × 1.3(그림 높이 ≈ 키 × 1.17 을 살짝 넘게) — 실측 머리 폭(배율 1.0) 파랑 2.34 · 주황 2.30 · 녹색 2.11 · 보라 2.05 단위 → `f_scale` 0.56 · 0.57 · 0.62 · 0.63 · 머리 중심 = 몸 앞 0.6 키(`f_fwd` · 유닛 아래 층이라 몸 위에 두면 그림에 가려짐) · 뒤쪽 Dash 0.4. 새 이펙트는 같은 방식으로 머리 폭을 재서 정한다(경계 상자 `bounds` 는 입자 시뮬레이션 범위라 실제보다 10배 큼 → 픽셀 측정).
- **색감 · 셀화(PD 2026-11-10 「게임 분위기와 안 맞음」 → Fable 검토 단독 권고 = 이식 셰이더 수정 · 원본 Master Stylized FX 빌트인 셰이더도 수식 동일)**: `TKS_HdrApprox.cginc` = 흰 섞임 상한 `TKS_HOT_MAX` 0.2 · 색 · 불투명도 3단계(`TKS_CEL_STEPS` · 불투명도 반올림 · 보강 `TKS_ALPHA_GAIN` 1.25) · `CommonMeshLoop` 뒷면 셸 = 같은 색 × `TKS_RIM_SHADE` 0.45(고정 갈색은 검은 얼룩으로 보여 바꿈) · 사본 프리팹 진영색 = 위 #3F7FD6 · 촉 #5FAE57 · 오 #E0633A · 군 #9A5BB5(원래 밝기 유지 · 흰 층 제외) · 발광 배수(CustomData Custom1.x) 1·2·5 → 1·1.3·2.
- **이펙트 사본 규칙(WL `WL_Fx_*` 와 같음)**: 게임이 읽는 것 = `Assets/Resources/Fx/TKS_Fx_<이름>.prefab` 사본만(메뉴 `TKS/VFX/Make Skill Fx Copy` · 고른 프리팹 → 사본) · 원본 팩 `Assets/VFX/**` 는 고치지 않는다.
- **WL 리소스 옮기기** = `Tools/wl_vfx_import.py`(scan · copy · unpack · `--wl <WL 루트>` · GUID 유지 · URP 전용 셰이더 재질 프리팹은 자동 제외 · WL 원본 무수정) → 에디터 새로고침 → 메뉴 `TKS/VFX/Check Imported VFX`(분홍 재질 · 빠진 스크립트). 판정 표 = `Assets/VFX/_wl_import_report.csv`. 2026-11-09 옮긴 것 = VFX_Klaus(Hyper Casual 1·2 · Shoot&Hit 1·2 · Earth Skills · Element Splash 3) · Lana Casual RPG VFX · Slash Effects FREE · EricWang 4묶음 중 빌트인 통과분 · MasterStylizedProjectiles 통과분 · Kyeoms 폭발 · GAP 툰 투사체(빌트인 패키지 unpack) · WL 창 돌진 사본 3(`Assets/VFX/WL/`) = 약 640 MB.
- **URP 셰이더 이식판** = `Assets/VFX/_BuiltinPorts/*.shader`(원본과 같은 GUID · 셰이더 이름 · 속성 → 옮겨 온 재질이 그대로 사용 · 현재 Eric AdditiveFlow · AlphaBlendFlow · MasterStylizedFX EmissiveParticle · CommonMesh2Side). 새로 필요한 URP 셰이더는 같은 방식으로 이식하면 `wl_vfx_import.py` 가 통과(port)로 본다.
- **카메라 깊이 텍스처 켬**(`GameBootstrap` · `SkillTestBootstrap` `depthTextureMode |= Depth`): Hyper Casual 등 셰이더 그래프의 소프트 파티클이 `_CameraDepthTexture` 를 읽음 — 없으면 이펙트가 안 보이거나 불투명 하늘색 덩어리(2026-11-09 실측). 셰이더 그래프 재질은 `com.unity.shadergraph 17.3.0`(Built-In 타깃) 필요.
## 유닛 아트 반영 (다른 세션의 원화·애니 → 이 게임)
- 아트 위치 = `Assets/Resources/Units/<art>/<clip>/f000.png …` + `meta.json`(fps · ppu · pivot(발) · charHeightPx · clips). `<art>` 는 `BalanceConfig.asset` 의 `heroes[].art` / `stages[].enemies[].art`(비면 도형). 아군 = `liubei`·`guanyu`·`zhangfei`·`zhaoyun`·`huangzhong`(2026-11-04 재디자인 원화 · 스켈레탈 B단계 전까지 front_idle 정지 1장 · 납품 후 리그 v2 12 클립으로 교체) · 적 = `<세력>_<병종>`(`hgj_infantry` …) · 보스 = `<세력>_boss_<로마자>`(`hgj_boss_zhangjiao` …).
- 스켈레탈 리그 → 프레임: 조직 레포 스킬 `skeletal-anim-pipeline/files/scripts/rig_frames_export.py rig <리그 패키지> --parts <작업>/parts --out Assets/Resources/Units/<art> --clips back_idle,back_ready,back_action,back_damage,back_stop,front_idle,front_ready,front_action,front_damage,front_stop,front_victory,front_failed --px 180 --step 2`(ComfyUI venv python · 실측 482×470 · 15 fps · 3인 11 MB).
- 정지 그림(적 SD 전신 `_rgba.png`) → `rig_frames_export.py static <png> --out Assets/Resources/Units/<art>`(front_idle 1장 · 발 = 불투명 상자 아래). 원화 세션 납품 = `C:\ArtRnD\samguk_game\manifest.json` · 파일명 `<art>_rgba.png` → `<art>` 폴더(2026-11-04 1차 `hgj_*` 5종 + 2차 `dz_infantry·spear·cavalry·archer` 반영 완료 · 여포 `dz_boss_lvbu`·`cc_*`·`wei_*` 는 납품 시 같은 명령).
- 전장 배경 = `Assets/Resources/Board/<boardArt>.png`(자산 `boardArt` · 5:8 불투명 · PPU 100 = 전장 20×32) → `Board.Build` 가 있으면 바닥으로 쓰고 격자·진영 띠는 생략. 임포트 규칙(스프라이트 · 4096 · 압축)은 같은 `UnitArtImporter` 의 `Board/` 분기 · 재임포트 메뉴가 두 폴더 처리.
- 임포트 설정은 `Editor/UnitArtImporter.cs`(AssetPostprocessor · meta 의 pivot·ppu) 가 자동 적용 · 프레임을 넣거나 meta 를 바꾼 뒤 = 메뉴 `TKS/Reimport Unit Art`(Build/BuildFresh 가 함께 호출).
- 재생 = `UnitArt.cs`(Resources 로더 · 캐시) + `FrameAnimator`(대기 loop · 준비 = 조준 중 · 공격·피격 = 1프레임 자세 유지 후 대기 · 승리 = 끝 프레임 유지). **포즈 규칙(PD #1084 · 2026-11-05)**: 공격 애니 대신 1프레임 포즈 전환 — 조준(당기는) 중 = `ready`(출발 자세) · 슬링샷 이동(돌진) 중 = `action`(병종 돌진 포즈 · `UnitView.SetCharging` · `ActionResolver.Resolve` 가 루틴 전후로 켜고 끔) · 멈추면 `idle`. 돌진 중 피격은 색만 바뀜. **4방향 보기(PD #1082)**: `UnitView.SetFacing(dir)` — 마지막 이동 방향의 세로 성분으로 `back_*`(위 ↖↗) / `front_*`(아래 ↙↘) 세트를, 가로 성분으로 `flipX`(왼쪽 = 반전 · 오른쪽 = 기본)를 정한다. 시작 = 아군 ↗ · 적군 ↘. `ActionResolver.Face()` 가 이동·튕김·밀림·돌진·사격·밀린 아군 공격 때 호출(기병 복귀는 방향 유지). 없는 클립은 다른 보기 → 대기로 대체. 세로 크기 = `unitArtHeight`(자산) × 반지름 배율 · 발 = 원 중심 아래 0.55 R.
## 구조 (README 와 동일)
- `Assets/Scripts/Runtime/` — `GameBootstrap`(씬 구성) · `BattleManager` · `ActionResolver` · `Combat` · `SlingshotInput`(조작·스크롤·줌) · `RangeIndicator`(사거리 원·궁병 도넛) · `CameraController`(넓은 전장 카메라) · `EnemyAI`(교전 범위) · `BattleHUD`(HUD·미니맵) · `Unit`/`UnitView` · `UnitArt`(아트 로더·`FrameAnimator`) · `Board` · `Fx` · `AutoPlayDriver`
- `Assets/Scripts/Editor/` — `ProjectSetup`(CLI 진입점·씬 생성) · `BalanceDefaults`(최초 시드) · `StageImporter`(스테이지 표 → 자산) · `UnitArtImporter`(아트 임포트 설정) · `DataImporter`(아웃게임 표 → 자산 · 화면 캡처)
- `Assets/Scripts/OutGame/` — 아웃게임(세이브·로비·영웅·편성·거래·보상 · 내정 `Domestic/` · 보물 `Treasure/` · 연무장 `Arena/` · 위 「코드 규칙」)
- `Assets/Data/Stages/` — 챕터·스테이지 표 CSV 4장(위 「코드 규칙」) · 해석·배치 = `Runtime/StageTable`
- `Assets/Resources/Units/<art>/` — 유닛 아트 프레임 + meta.json(위 절)
- `Assets/Scripts/Runtime/Skills/` — 스킬 처리기(위 「코드 규칙」 스킬)
- `Assets/Scripts/Runtime/Gimmicks/` — 지형 기믹 레이어(위 「코드 규칙」) · `Assets/Scripts/Runtime/AI/` — 예측 시뮬레이션 적 AI(`Sim` · `TacticalAI` · `AiWeights`)
- `Assets/Data/Gimmicks/` · `Assets/Data/AI/` — 기믹 표 3장 · AI 가중치 표
- `Assets/Tests/PlayMode/` — `BattleFlowTests` · `OutGameTests` · `SkillTests` · `OutGameCTests` · `GimmickTests` · `TacticalAITests` · `UILayerTests`(캔버스 층·아틀라스 구조 · 2026-11-10 · 전체 119)
- `.claude/skills/` — Unity CLI 에이전트 스킬(`unity skill install claude-code --local` 산출물 · 재설치는 `unity skill refresh`)